Restructure everything, native is now top level and app is written in kotlin with new Jetpack Compose ui

This commit is contained in:
Nathan Adams 2024-03-16 22:48:03 +01:00 committed by TÖRÖK Attila
parent 454a80bc66
commit e395c087b4
75 changed files with 1282 additions and 580 deletions

View File

@ -44,8 +44,7 @@ jobs:
- name: Build native libs
run: |
unset ANDROID_SDK_ROOT # Deprecated, will cause an error if left set.
cd native
cargo ndk --target ${{ matrix.android-abi }} --platform 26 -o ../jniLibs build --release
cargo ndk --target ${{ matrix.android-abi }} --platform 26 -o jniLibs build --release
- uses: actions/upload-artifact@v4
with:
@ -65,8 +64,8 @@ jobs:
- name: Copy native libs
run: |
mkdir app/ruffle/src/main/jniLibs
cp -r native-libs/*/* app/ruffle/src/main/jniLibs/
mkdir app/src/main/jniLibs
cp -r native-libs/*/* app/src/main/jniLibs/
- name: Set up Java 17
uses: actions/setup-java@v4
@ -77,21 +76,19 @@ jobs:
- name: Build debug APK
# The native libs are always built in release mode, this is left in here just so if
# something with the signing procedure below goes haywire, we still have something.
run: |
cd app
./gradlew assembleDebug # The release version doesn't work without signing
run: ./gradlew assembleDebug # The release version doesn't work without signing
- uses: actions/upload-artifact@v4
with:
name: ruffle-debug-apks
path: app/ruffle/build/outputs/apk/debug/*.apk
path: app/build/outputs/apk/debug/*.apk
- name: Decode keystore
if: ${{ !github.event.pull_request.head.repo.fork }}
env:
ENCODED_STRING: ${{ secrets.KEYSTORE }}
run: |
echo $ENCODED_STRING | base64 -di > app/ruffle/androidkey.jks
echo $ENCODED_STRING | base64 -di > app/androidkey.jks
- name: Build release APK
if: ${{ !github.event.pull_request.head.repo.fork }}
@ -107,5 +104,5 @@ jobs:
if: ${{ !github.event.pull_request.head.repo.fork }}
with:
name: ruffle-release-apks
path: app/ruffle/build/outputs/apk/release/*.apk
path: app/build/outputs/apk/release/*.apk

12
.gitignore vendored Normal file
View File

@ -0,0 +1,12 @@
*.iml
.gradle
/local.properties
.idea
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
/target
*.jks

View File

15
app/.gitignore vendored
View File

@ -1,15 +1,2 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
/src/main/jniLibs

View File

@ -1,3 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml

View File

@ -1 +0,0 @@
Ruffle

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="17" />
</component>
</project>

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<value>
<entry key="ruffle">
<State />
</entry>
</value>
</component>
</project>

View File

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/ruffle" />
</set>
</option>
<option name="resolveExternalAnnotations" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectMigrations">
<option name="MigrateToGradleLocalJavaHome">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</component>
</project>

View File

@ -1,30 +0,0 @@
<project version="4">
<component name="DesignSurface">
<option name="filePathToZoomLevelMap">
<map>
<entry key="../../../../../layout/custom_preview.xml" value="0.2520833333333333" />
<entry key="ruffle/src/main/res/drawable-v24/ic_launcher_foreground.xml" value="0.25" />
<entry key="ruffle/src/main/res/drawable/ic_baseline_menu_24.xml" value="0.1875" />
<entry key="ruffle/src/main/res/drawable/ic_launcher_background.xml" value="0.2555" />
<entry key="ruffle/src/main/res/drawable/ic_launcher_foreground.xml" value="0.1875" />
<entry key="ruffle/src/main/res/drawable/ic_logo.xml" value="0.1875" />
<entry key="ruffle/src/main/res/layout-port-navhidden/keyboard.xml" value="0.14492753623188406" />
<entry key="ruffle/src/main/res/layout-port-navhidden/ruffle_keyboard.xml" value="0.18" />
<entry key="ruffle/src/main/res/layout/activity_main.xml" value="0.25" />
<entry key="ruffle/src/main/res/layout/ggg.xml" value="0.11141304347826086" />
<entry key="ruffle/src/main/res/layout/keyboard.xml" value="0.25" />
<entry key="ruffle/src/main/res/layout/overlay_layout.xml" value="0.1480978260869565" />
<entry key="ruffle/src/main/res/layout/pog.xml" value="0.11141304347826086" />
<entry key="ruffle/src/main/res/layout/test.xml" value="0.10054347826086957" />
<entry key="ruffle/src/main/res/mipmap-anydpi-v26/ic_launcher.xml" value="0.1875" />
<entry key="ruffle/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml" value="0.2555" />
</map>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

View File

@ -1,9 +0,0 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id 'com.android.application' version '8.2.1' apply false
id 'com.android.library' version '8.2.1' apply false
}
task clean(type: Delete) {
delete rootProject.buildDir
}

124
app/build.gradle.kts Normal file
View File

@ -0,0 +1,124 @@
plugins {
alias(libs.plugins.androidApplication)
alias(libs.plugins.jetbrainsKotlinAndroid)
alias(libs.plugins.cargoNdkAndroid).apply(System.getenv("GITHUB_ACTIONS") == null)
}
android {
namespace = "rs.ruffle"
compileSdk = 34
defaultConfig {
applicationId = "rs.ruffle"
minSdk = 26
targetSdk = 34
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
ndk {
abiFilters.addAll(listOf("arm64-v8a", "armeabi-v7a", "x86_64", "x86"))
}
}
signingConfigs {
create("release") {
storeFile = file("androidkey.jks")
storePassword = System.getenv("SIGNING_STORE_PASSWORD")
keyAlias = System.getenv("SIGNING_KEY_ALIAS")
keyPassword = System.getenv("SIGNING_KEY_PASSWORD")
}
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
signingConfig = signingConfigs.findByName("release")
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
compose = true
prefab = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.1"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
splits {
// Configures multiple APKs based on ABI.
abi {
// Enables building multiple APKs per ABI.
isEnable = true
// Resets the list of ABIs that Gradle should create APKs for to none.
reset()
// Specifies a list of ABIs that Gradle should create APKs for.
include("arm64-v8a", "armeabi-v7a", "x86_64", "x86")
// Specifies that we also want to generate a universal APK that includes all ABIs.
isUniversalApk = true
}
}
}
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.ui)
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.androidx.navigation.runtime.ktx)
implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.games.activity)
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.appcompat)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
}
// On GHA, we prebuild the native libs separately for fasterness,
// and this plugin doesn't recognize them, so would build them again.
if (System.getenv("GITHUB_ACTIONS") == null) {
cargoNdk {
module = "."
apiLevel = 26
buildType = "release"
}
}

View File

@ -1,2 +0,0 @@
/build
src/main/jniLibs/*

View File

@ -1,102 +0,0 @@
plugins {
id 'com.android.application'
id "com.github.willir.rust.cargo-ndk-android" version "0.3.4" apply false
}
android {
compileSdk 34
buildFeatures {
prefab true
}
defaultConfig {
applicationId "rs.ruffle"
minSdk 26
targetSdk 33
versionCode 1
versionName "1.0"
ndk {
abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86_64', 'x86'
}
}
signingConfigs {
release {
storeFile = file("androidkey.jks")
storePassword System.getenv("SIGNING_STORE_PASSWORD")
keyAlias System.getenv("SIGNING_KEY_ALIAS")
keyPassword System.getenv("SIGNING_KEY_PASSWORD")
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
namespace 'rs.ruffle'
splits {
// Configures multiple APKs based on ABI.
abi {
// Enables building multiple APKs per ABI.
enable true
// Resets the list of ABIs that Gradle should create APKs for to none.
reset()
// Specifies a list of ABIs that Gradle should create APKs for.
include 'arm64-v8a', 'armeabi-v7a', 'x86_64', 'x86'
// Specifies that we also want to generate a universal APK that includes all ABIs.
universalApk true
}
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.11.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
// To use the Android Frame Pacing library
//implementation "androidx.games:games-frame-pacing:1.9.1"
// To use the Android Performance Tuner
//implementation "androidx.games:games-performance-tuner:1.5.0"
// To use the Games Activity library
implementation "androidx.games:games-activity:2.0.2"
// To use the Games Controller Library
//implementation "androidx.games:games-controller:1.1.0"
// To use the Games Text Input Library
//implementation "androidx.games:games-text-input:1.1.0"
}
// On GHA, we prebuild the native libs separately for fasterness,
// and this plugin doesn't recognize them, so would build them again.
if (System.getenv("GITHUB_ACTIONS") == null) {
apply plugin: "com.github.willir.rust.cargo-ndk-android"
cargoNdk {
module = "../native"
apiLevel = 26
buildType = "release"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@ -1,137 +0,0 @@
package rs.ruffle;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import android.content.ContentResolver;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.TextView;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class MainActivity extends AppCompatActivity {
protected void startFromContentUri(Uri uri) {
ContentResolver resolver = getContentResolver();
try {
InputStream inputStream = resolver.openInputStream(uri);
int available = inputStream.available();
byte[] bytes = new byte[available];
// assuming the whole contents will be available at once
int _num_bytes_read = inputStream.read(bytes);
FullscreenNativeActivity.SWF_BYTES = bytes;
}
catch (IOException e) {
}
Intent intent = new Intent(MainActivity.this, FullscreenNativeActivity.class);
startActivity(intent);
}
void startFromHttpUrl(String url) {
new Thread(() -> {
try {
HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
urlConnection.connect();
int length = urlConnection.getContentLength();
Log.i("rfl", "content length: " + length);
byte[] bytes = new byte[length];
int offs = 0;
try {
InputStream in = new BufferedInputStream(urlConnection.getInputStream());
while (offs < length) {
int read = in.read(bytes, offs, length-offs);
offs += read;
if (read > 0)
Log.i("rfl", "read " + read + " bytes");
}
Log.i("rfl", "read done: " + offs);
FullscreenNativeActivity.SWF_BYTES = bytes;
Intent intent = new Intent(MainActivity.this, FullscreenNativeActivity.class);
startActivity(intent);
} finally {
urlConnection.disconnect();
}
} catch (IOException e) {
Log.i("rfl", "ioerror e " + e);
}
}).start();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestNoStatusBarFeature();
setContentView(R.layout.activity_main);
hideActionBar();
if (Intent.ACTION_VIEW.equals(getIntent().getAction())) {
Uri uri = getIntent().getData();
if ("https".equals(uri.getScheme()) || "http".equals(uri.getScheme()))
startFromHttpUrl(uri.toString());
else
startFromContentUri(uri);
}
ActivityResultLauncher launcher = registerForActivityResult(
new ActivityResultContracts.GetContent(),
uri -> startFromContentUri(uri)
);
View button = findViewById(R.id.button);
button.setOnClickListener((event) -> {
launcher.launch("application/x-shockwave-flash");
});
View button3 = findViewById(R.id.button3);
button3.setOnClickListener((event) -> {
EditText swfUrl = findViewById(R.id.editTextSwfUrl);
startFromHttpUrl(swfUrl.getText().toString());
});
}
private void requestNoStatusBarFeature() {
//Hiding the status bar this way makes it see through when pulled down
requestWindowFeature(Window.FEATURE_NO_TITLE);
this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
private void hideActionBar() {
ActionBar actionBar = getSupportActionBar();
if (actionBar != null)
actionBar.hide();
}
}

View File

@ -1,75 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:ruffle="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#37528C"
tools:context=".MainActivity">
<Button
android:id="@+id/button"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="136dp"
android:layout_height="48dp"
android:background="#FFAD33"
android:backgroundTint="#FFAD33"
android:foregroundTint="#FFAD33"
android:text="@string/load_an_swf"
android:textColor="@color/black"
ruffle:layout_constraintBottom_toTopOf="@+id/linearLayout"
ruffle:layout_constraintEnd_toEndOf="parent"
ruffle:layout_constraintHorizontal_bias="0.498"
ruffle:layout_constraintStart_toStartOf="parent"
ruffle:layout_constraintTop_toBottomOf="@+id/imageView"
ruffle:rippleColor="#FFAD33" />
<ImageView
android:id="@+id/imageView"
android:layout_width="308dp"
android:layout_height="108dp"
android:contentDescription="@string/ruffle_logo_description"
ruffle:layout_constraintBottom_toTopOf="@+id/button"
ruffle:layout_constraintEnd_toEndOf="parent"
ruffle:layout_constraintHorizontal_bias="0.5"
ruffle:layout_constraintStart_toStartOf="parent"
ruffle:layout_constraintTop_toTopOf="parent"
ruffle:layout_constraintVertical_chainStyle="spread"
ruffle:srcCompat="@drawable/ic_logo" />
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="143dp"
android:orientation="horizontal"
ruffle:layout_constraintBottom_toBottomOf="parent"
ruffle:layout_constraintEnd_toEndOf="parent"
ruffle:layout_constraintHorizontal_bias="0.492"
ruffle:layout_constraintStart_toStartOf="parent"
ruffle:layout_constraintTop_toBottomOf="@+id/button">
<EditText
android:id="@+id/editTextSwfUrl"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="textUri"
android:minHeight="48dp"
android:text="SWF URL" />
<Button
android:id="@+id/button3"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="136dp"
android:layout_height="48dp"
android:layout_marginBottom="16dp"
android:background="#FFAD33"
android:backgroundTint="#FFAD33"
android:foregroundTint="#FFAD33"
android:text="@string/open_url"
android:textColor="@color/black"
ruffle:rippleColor="#FFAD33" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,16 +0,0 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.Ruffle" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@ -1,7 +0,0 @@
<resources>
<string name="app_name">Ruffle</string>
<string name="load_an_swf">Load an SWF</string>
<string name="open_url">Open URL</string>
<string name="ruffle">Ruffle</string>
<string name="ruffle_logo_description">Ruffle logo</string>
</resources>

View File

@ -1,16 +0,0 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.Ruffle" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@ -0,0 +1,24 @@
package rs.ruffle
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("rs.ruffle", appContext.packageName)
}
}

View File

@ -1,74 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET">
</uses-permission>
<!-- android:allowNativeHeapPointerTagging="false" -->
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Ruffle"
>
<activity
android:name=".MainActivity"
android:launchMode="singleTask"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.OPENABLE" />
<data android:scheme="file"/>
<data android:scheme="content"/>
<data android:mimeType="application/x-shockwave-flash"/>
<data android:pathSuffix="swf" />
<data android:pathPattern="*.swf" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="*" />
<data android:pathPattern=".*\\.swf" />
</intent-filter>
</activity>
<!-- no workyyy -->
<!-- android:windowSoftInputMode="stateAlwaysVisible|adjustResize" -->
<activity
android:name=".FullscreenNativeActivity"
android:exported="true"
android:configChanges="orientation|keyboardHidden|screenSize"
android:screenOrientation="user"
>
<meta-data android:name="android.app.lib_name" android:value="ruffle_android" />
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".MainActivity" />
</activity>
</application>
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Ruffle"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:launchMode="singleTask"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.OPENABLE" />
<data android:scheme="file"/>
<data android:scheme="content"/>
<data android:mimeType="application/x-shockwave-flash"/>
<data android:pathSuffix="swf" />
<data android:pathPattern="*.swf" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="*" />
<data android:pathPattern=".*\\.swf" />
</intent-filter>
</activity>
<activity
android:name=".FullscreenNativeActivity"
android:exported="true"
android:configChanges="orientation|keyboardHidden|screenSize"
android:screenOrientation="user"
>
<meta-data android:name="android.app.lib_name" android:value="ruffle_android" />
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".MainActivity" />
</activity>
</application>
</manifest>

View File

@ -1,20 +1,23 @@
package rs.ruffle;
import com.google.androidgamesdk.GameActivity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.PopupMenu;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.view.ViewCompat;
@ -22,12 +25,8 @@ import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.core.view.WindowInsetsControllerCompat;
import android.content.pm.PackageManager;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.view.View;
import android.widget.Button;
import android.widget.PopupMenu;
import com.google.androidgamesdk.GameActivity;
import java.util.ArrayList;
import java.util.List;

View File

@ -0,0 +1,44 @@
package rs.ruffle
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import rs.ruffle.ui.theme.RuffleTheme
import java.io.IOException
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
setContent {
RuffleTheme {
RuffleNavHost(openSwf = { openSwf(it) })
}
}
}
private fun openSwf(uri: Uri) {
val resolver = contentResolver
try {
val inputStream = resolver.openInputStream(uri)
inputStream.use {
val available = inputStream!!.available()
val bytes = ByteArray(available)
// assuming the whole contents will be available at once
inputStream.read(bytes)
FullscreenNativeActivity.SWF_BYTES = bytes
}
} catch (_: IOException) {
}
val intent = Intent(
this@MainActivity,
FullscreenNativeActivity::class.java
)
startActivity(intent)
}
}

View File

@ -0,0 +1,29 @@
package rs.ruffle
import android.net.Uri
import androidx.compose.runtime.Composable
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
object Destinations {
const val SELECT_SWF_ROUTE = "select"
}
@Composable
fun RuffleNavHost(
navController: NavHostController = rememberNavController(),
openSwf: (uri: Uri) -> Unit
) {
NavHost(
navController = navController,
startDestination = Destinations.SELECT_SWF_ROUTE,
) {
composable(Destinations.SELECT_SWF_ROUTE) {
SelectSwfRoute(
openSwf = openSwf
)
}
}
}

View File

@ -0,0 +1,200 @@
package rs.ruffle
import android.content.res.Configuration
import android.net.Uri
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.paddingFromBaseline
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import rs.ruffle.ui.theme.RuffleTheme
import rs.ruffle.ui.theme.slightlyDeemphasizedAlpha
@Composable
fun BrandBar() {
Image(
painter = painterResource(id = R.drawable.ic_logo_dark),
contentDescription = stringResource(id = R.string.logo_description),
modifier = Modifier
.padding(horizontal = 76.dp)
)
}
@Composable
fun SelectSwfRoute(
openSwf: (uri: Uri) -> Unit
) {
SelectSwfScreen(
openSwf = openSwf
)
}
@Composable
fun SelectSwfScreen(openSwf: (uri: Uri) -> Unit) {
Scaffold { innerPadding ->
Column(
modifier = Modifier.padding(innerPadding)
) {
Column(
modifier = Modifier.wrapContentHeight(align = Alignment.CenterVertically)
) {
BrandBar()
}
Text(
modifier = Modifier.padding(horizontal = 8.dp, vertical = 75.dp),
text = stringResource(id = R.string.work_in_progress_warning)
)
SelectSwfUrlOrFile(openSwf)
}
}
}
@Composable
private fun SelectSwfUrlOrFile(
openSwf: (uri: Uri) -> Unit
) {
val urlState by rememberSaveable(stateSaver = UrlStateSaver) {
mutableStateOf(UrlState())
}
Column(
modifier = Modifier
.padding(horizontal = 20.dp)
) {
val submitUrl = {
if (urlState.isValid) {
openSwf(Uri.parse(urlState.text))
} else {
urlState.enableShowErrors()
}
}
OutlinedTextField(
value = urlState.text,
onValueChange = { urlState.text = it },
modifier = Modifier
.fillMaxWidth()
.onFocusChanged { focusState ->
urlState.onFocusChange(focusState.isFocused)
if (!focusState.isFocused) {
urlState.enableShowErrors()
}
},
label = {
Text(
text = stringResource(id = R.string.url),
style = MaterialTheme.typography.bodyMedium,
)
},
textStyle = MaterialTheme.typography.bodyMedium,
isError = urlState.showErrors(),
keyboardOptions = KeyboardOptions.Default.copy(
imeAction = ImeAction.Go,
keyboardType = KeyboardType.Uri
),
keyboardActions = KeyboardActions(
onDone = { submitUrl() }
),
singleLine = true
)
urlState.getError()?.let { error -> TextFieldError(textError = error) }
Button(
onClick = submitUrl,
modifier = Modifier
.fillMaxWidth()
.padding(top = 28.dp, bottom = 3.dp)
) {
Text(
text = stringResource(id = R.string.open_url),
style = MaterialTheme.typography.titleSmall
)
}
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = stringResource(id = R.string.or),
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = slightlyDeemphasizedAlpha),
modifier = Modifier.paddingFromBaseline(top = 25.dp)
)
PickSwfButton(openSwf)
}
}
}
@Composable
fun PickSwfButton(onSelect: (uri: Uri) -> Unit) {
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) {
if (it != null) {
onSelect(it)
}
}
OutlinedButton(
onClick = {
launcher.launch(
"application/x-shockwave-flash"
)
},
modifier = Modifier
.fillMaxWidth()
.padding(top = 20.dp, bottom = 24.dp),
) {
Text(text = stringResource(id = R.string.select_a_swf))
}
}
@Composable
fun TextFieldError(textError: String) {
Row(modifier = Modifier.fillMaxWidth()) {
Spacer(modifier = Modifier.width(16.dp))
Text(
text = textError,
modifier = Modifier.fillMaxWidth(),
color = MaterialTheme.colorScheme.error
)
}
}
@Preview(name = "Select SWF - Light", uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(name = "Select SWF - Dark", uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
fun SelectSwfScreenPreview() {
RuffleTheme {
Surface {
SelectSwfScreen(
openSwf = {}
)
}
}
}

View File

@ -0,0 +1,53 @@
package rs.ruffle
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.listSaver
import androidx.compose.runtime.setValue
open class TextFieldState(
private val validator: (String) -> Boolean = { true },
private val errorFor: (String) -> String = { "" }
) {
var text: String by mutableStateOf("")
// was the TextField ever focused
var isFocusedDirty: Boolean by mutableStateOf(false)
var isFocused: Boolean by mutableStateOf(false)
private var displayErrors: Boolean by mutableStateOf(false)
open val isValid: Boolean
get() = validator(text)
fun onFocusChange(focused: Boolean) {
isFocused = focused
if (focused) isFocusedDirty = true
}
fun enableShowErrors() {
// only show errors if the text was at least once focused
if (isFocusedDirty) {
displayErrors = true
}
}
fun showErrors() = !isValid && displayErrors
open fun getError(): String? {
return if (showErrors()) {
errorFor(text)
} else {
null
}
}
}
fun textFieldStateSaver(state: TextFieldState) = listSaver<TextFieldState, Any>(
save = { listOf(it.text, it.isFocusedDirty) },
restore = {
state.apply {
text = it[0] as String
isFocusedDirty = it[1] as Boolean
}
}
)

View File

@ -0,0 +1,22 @@
package rs.ruffle
import android.util.Patterns
class UrlState(val url: String? = null) :
TextFieldState(validator = ::isUrlValid, errorFor = ::urlValidationError) {
init {
url?.let {
text = it
}
}
}
private fun urlValidationError(url: String): String {
return "Invalid url: $url"
}
private fun isUrlValid(url: String): Boolean {
return Patterns.WEB_URL.matcher(url).matches()
}
val UrlStateSaver = textFieldStateSaver(UrlState())

View File

@ -0,0 +1,228 @@
package rs.ruffle.ui.theme
import androidx.compose.ui.graphics.Color
val ruffleBlue = Color(0xFF37528c)
val primaryLight = Color(0xFF845400)
val onPrimaryLight = Color(0xFFFFFFFF)
val primaryContainerLight = Color(0xFFFFB858)
val onPrimaryContainerLight = Color(0xFF4D2F00)
val secondaryLight = Color(0xFF7A582A)
val onSecondaryLight = Color(0xFFFFFFFF)
val secondaryContainerLight = Color(0xFFFFD5A4)
val onSecondaryContainerLight = Color(0xFF5D3E12)
val tertiaryLight = Color(0xFF213D76)
val onTertiaryLight = Color(0xFFFFFFFF)
val tertiaryContainerLight = Color(0xFF47619C)
val onTertiaryContainerLight = Color(0xFFFFFFFF)
val errorLight = Color(0xFFBA1A1A)
val onErrorLight = Color(0xFFFFFFFF)
val errorContainerLight = Color(0xFFFFDAD6)
val onErrorContainerLight = Color(0xFF410002)
val backgroundLight = Color(0xFFFFF8F4)
val onBackgroundLight = Color(0xFF211A12)
val surfaceLight = Color(0xFFFFF8F4)
val onSurfaceLight = Color(0xFF211A12)
val surfaceVariantLight = Color(0xFFF4DFC9)
val onSurfaceVariantLight = Color(0xFF524434)
val outlineLight = Color(0xFF857462)
val outlineVariantLight = Color(0xFFD7C3AE)
val scrimLight = Color(0xFF000000)
val inverseSurfaceLight = Color(0xFF372F26)
val inverseOnSurfaceLight = Color(0xFFFDEEE1)
val inversePrimaryLight = Color(0xFFFFB95B)
val surfaceDimLight = Color(0xFFE6D8CA)
val surfaceBrightLight = Color(0xFFFFF8F4)
val surfaceContainerLowestLight = Color(0xFFFFFFFF)
val surfaceContainerLowLight = Color(0xFFFFF1E5)
val surfaceContainerLight = Color(0xFFFAEBDE)
val surfaceContainerHighLight = Color(0xFFF4E6D8)
val surfaceContainerHighestLight = Color(0xFFEFE0D3)
val primaryLightMediumContrast = Color(0xFF5F3B00)
val onPrimaryLightMediumContrast = Color(0xFFFFFFFF)
val primaryContainerLightMediumContrast = Color(0xFFA26800)
val onPrimaryContainerLightMediumContrast = Color(0xFFFFFFFF)
val secondaryLightMediumContrast = Color(0xFF5B3D11)
val onSecondaryLightMediumContrast = Color(0xFFFFFFFF)
val secondaryContainerLightMediumContrast = Color(0xFF936E3E)
val onSecondaryContainerLightMediumContrast = Color(0xFFFFFFFF)
val tertiaryLightMediumContrast = Color(0xFF213D76)
val onTertiaryLightMediumContrast = Color(0xFFFFFFFF)
val tertiaryContainerLightMediumContrast = Color(0xFF47619C)
val onTertiaryContainerLightMediumContrast = Color(0xFFFFFFFF)
val errorLightMediumContrast = Color(0xFF8C0009)
val onErrorLightMediumContrast = Color(0xFFFFFFFF)
val errorContainerLightMediumContrast = Color(0xFFDA342E)
val onErrorContainerLightMediumContrast = Color(0xFFFFFFFF)
val backgroundLightMediumContrast = Color(0xFFFFF8F4)
val onBackgroundLightMediumContrast = Color(0xFF211A12)
val surfaceLightMediumContrast = Color(0xFFFFF8F4)
val onSurfaceLightMediumContrast = Color(0xFF211A12)
val surfaceVariantLightMediumContrast = Color(0xFFF4DFC9)
val onSurfaceVariantLightMediumContrast = Color(0xFF4E4031)
val outlineLightMediumContrast = Color(0xFF6C5C4B)
val outlineVariantLightMediumContrast = Color(0xFF897866)
val scrimLightMediumContrast = Color(0xFF000000)
val inverseSurfaceLightMediumContrast = Color(0xFF372F26)
val inverseOnSurfaceLightMediumContrast = Color(0xFFFDEEE1)
val inversePrimaryLightMediumContrast = Color(0xFFFFB95B)
val surfaceDimLightMediumContrast = Color(0xFFE6D8CA)
val surfaceBrightLightMediumContrast = Color(0xFFFFF8F4)
val surfaceContainerLowestLightMediumContrast = Color(0xFFFFFFFF)
val surfaceContainerLowLightMediumContrast = Color(0xFFFFF1E5)
val surfaceContainerLightMediumContrast = Color(0xFFFAEBDE)
val surfaceContainerHighLightMediumContrast = Color(0xFFF4E6D8)
val surfaceContainerHighestLightMediumContrast = Color(0xFFEFE0D3)
val primaryLightHighContrast = Color(0xFF331E00)
val onPrimaryLightHighContrast = Color(0xFFFFFFFF)
val primaryContainerLightHighContrast = Color(0xFF5F3B00)
val onPrimaryContainerLightHighContrast = Color(0xFFFFFFFF)
val secondaryLightHighContrast = Color(0xFF331E00)
val onSecondaryLightHighContrast = Color(0xFFFFFFFF)
val secondaryContainerLightHighContrast = Color(0xFF5B3D11)
val onSecondaryContainerLightHighContrast = Color(0xFFFFFFFF)
val tertiaryLightHighContrast = Color(0xFF002051)
val onTertiaryLightHighContrast = Color(0xFFFFFFFF)
val tertiaryContainerLightHighContrast = Color(0xFF25417A)
val onTertiaryContainerLightHighContrast = Color(0xFFFFFFFF)
val errorLightHighContrast = Color(0xFF4E0002)
val onErrorLightHighContrast = Color(0xFFFFFFFF)
val errorContainerLightHighContrast = Color(0xFF8C0009)
val onErrorContainerLightHighContrast = Color(0xFFFFFFFF)
val backgroundLightHighContrast = Color(0xFFFFF8F4)
val onBackgroundLightHighContrast = Color(0xFF211A12)
val surfaceLightHighContrast = Color(0xFFFFF8F4)
val onSurfaceLightHighContrast = Color(0xFF000000)
val surfaceVariantLightHighContrast = Color(0xFFF4DFC9)
val onSurfaceVariantLightHighContrast = Color(0xFF2D2214)
val outlineLightHighContrast = Color(0xFF4E4031)
val outlineVariantLightHighContrast = Color(0xFF4E4031)
val scrimLightHighContrast = Color(0xFF000000)
val inverseSurfaceLightHighContrast = Color(0xFF372F26)
val inverseOnSurfaceLightHighContrast = Color(0xFFFFFFFF)
val inversePrimaryLightHighContrast = Color(0xFFFFE8D1)
val surfaceDimLightHighContrast = Color(0xFFE6D8CA)
val surfaceBrightLightHighContrast = Color(0xFFFFF8F4)
val surfaceContainerLowestLightHighContrast = Color(0xFFFFFFFF)
val surfaceContainerLowLightHighContrast = Color(0xFFFFF1E5)
val surfaceContainerLightHighContrast = Color(0xFFFAEBDE)
val surfaceContainerHighLightHighContrast = Color(0xFFF4E6D8)
val surfaceContainerHighestLightHighContrast = Color(0xFFEFE0D3)
val primaryDark = Color(0xFFFFDCB5)
val onPrimaryDark = Color(0xFF462A00)
val primaryContainerDark = Color(0xFFF8A72D)
val onPrimaryContainerDark = Color(0xFF402600)
val secondaryDark = Color(0xFFECBF87)
val onSecondaryDark = Color(0xFF462B01)
val secondaryContainerDark = Color(0xFF55370C)
val onSecondaryContainerDark = Color(0xFFF8C991)
val tertiaryDark = Color(0xFFB0C6FF)
val onTertiaryDark = Color(0xFF0D2E66)
val tertiaryContainerDark = Color(0xFF2D4882)
val onTertiaryContainerDark = Color(0xFFDDE5FF)
val errorDark = Color(0xFFFFB4AB)
val onErrorDark = Color(0xFF690005)
val errorContainerDark = Color(0xFF93000A)
val onErrorContainerDark = Color(0xFFFFDAD6)
val backgroundDark = Color(0xFF19120B)
val onBackgroundDark = Color(0xFFEFE0D3)
val surfaceDark = Color(0xFF19120B)
val onSurfaceDark = Color(0xFFEFE0D3)
val surfaceVariantDark = Color(0xFF524434)
val onSurfaceVariantDark = Color(0xFFD7C3AE)
val outlineDark = Color(0xFF9F8E7B)
val outlineVariantDark = Color(0xFF524434)
val scrimDark = Color(0xFF000000)
val inverseSurfaceDark = Color(0xFFEFE0D3)
val inverseOnSurfaceDark = Color(0xFF372F26)
val inversePrimaryDark = Color(0xFF845400)
val surfaceDimDark = Color(0xFF19120B)
val surfaceBrightDark = Color(0xFF40382E)
val surfaceContainerLowestDark = Color(0xFF130D06)
val surfaceContainerLowDark = Color(0xFF211A12)
val surfaceContainerDark = Color(0xFF261E16)
val surfaceContainerHighDark = Color(0xFF302920)
val surfaceContainerHighestDark = Color(0xFF3C332A)
val primaryDarkMediumContrast = Color(0xFFFFDCB5)
val onPrimaryDarkMediumContrast = Color(0xFF3D2400)
val primaryContainerDarkMediumContrast = Color(0xFFF8A72D)
val onPrimaryContainerDarkMediumContrast = Color(0xFF000000)
val secondaryDarkMediumContrast = Color(0xFFF1C38B)
val onSecondaryDarkMediumContrast = Color(0xFF231300)
val secondaryContainerDarkMediumContrast = Color(0xFFB28957)
val onSecondaryContainerDarkMediumContrast = Color(0xFF000000)
val tertiaryDarkMediumContrast = Color(0xFFB6CAFF)
val onTertiaryDarkMediumContrast = Color(0xFF00143A)
val tertiaryContainerDarkMediumContrast = Color(0xFF7690CE)
val onTertiaryContainerDarkMediumContrast = Color(0xFF000000)
val errorDarkMediumContrast = Color(0xFFFFBAB1)
val onErrorDarkMediumContrast = Color(0xFF370001)
val errorContainerDarkMediumContrast = Color(0xFFFF5449)
val onErrorContainerDarkMediumContrast = Color(0xFF000000)
val backgroundDarkMediumContrast = Color(0xFF19120B)
val onBackgroundDarkMediumContrast = Color(0xFFEFE0D3)
val surfaceDarkMediumContrast = Color(0xFF19120B)
val onSurfaceDarkMediumContrast = Color(0xFFFFFAF7)
val surfaceVariantDarkMediumContrast = Color(0xFF524434)
val onSurfaceVariantDarkMediumContrast = Color(0xFFDCC7B2)
val outlineDarkMediumContrast = Color(0xFFB2A08C)
val outlineVariantDarkMediumContrast = Color(0xFF91806E)
val scrimDarkMediumContrast = Color(0xFF000000)
val inverseSurfaceDarkMediumContrast = Color(0xFFEFE0D3)
val inverseOnSurfaceDarkMediumContrast = Color(0xFF302920)
val inversePrimaryDarkMediumContrast = Color(0xFF664000)
val surfaceDimDarkMediumContrast = Color(0xFF19120B)
val surfaceBrightDarkMediumContrast = Color(0xFF40382E)
val surfaceContainerLowestDarkMediumContrast = Color(0xFF130D06)
val surfaceContainerLowDarkMediumContrast = Color(0xFF211A12)
val surfaceContainerDarkMediumContrast = Color(0xFF261E16)
val surfaceContainerHighDarkMediumContrast = Color(0xFF302920)
val surfaceContainerHighestDarkMediumContrast = Color(0xFF3C332A)
val primaryDarkHighContrast = Color(0xFFFFFAF7)
val onPrimaryDarkHighContrast = Color(0xFF000000)
val primaryContainerDarkHighContrast = Color(0xFFFFBF6B)
val onPrimaryContainerDarkHighContrast = Color(0xFF000000)
val secondaryDarkHighContrast = Color(0xFFFFFAF7)
val onSecondaryDarkHighContrast = Color(0xFF000000)
val secondaryContainerDarkHighContrast = Color(0xFFF1C38B)
val onSecondaryContainerDarkHighContrast = Color(0xFF000000)
val tertiaryDarkHighContrast = Color(0xFFFCFAFF)
val onTertiaryDarkHighContrast = Color(0xFF000000)
val tertiaryContainerDarkHighContrast = Color(0xFFB6CAFF)
val onTertiaryContainerDarkHighContrast = Color(0xFF000000)
val errorDarkHighContrast = Color(0xFFFFF9F9)
val onErrorDarkHighContrast = Color(0xFF000000)
val errorContainerDarkHighContrast = Color(0xFFFFBAB1)
val onErrorContainerDarkHighContrast = Color(0xFF000000)
val backgroundDarkHighContrast = Color(0xFF19120B)
val onBackgroundDarkHighContrast = Color(0xFFEFE0D3)
val surfaceDarkHighContrast = Color(0xFF19120B)
val onSurfaceDarkHighContrast = Color(0xFFFFFFFF)
val surfaceVariantDarkHighContrast = Color(0xFF524434)
val onSurfaceVariantDarkHighContrast = Color(0xFFFFFAF7)
val outlineDarkHighContrast = Color(0xFFDCC7B2)
val outlineVariantDarkHighContrast = Color(0xFFDCC7B2)
val scrimDarkHighContrast = Color(0xFF000000)
val inverseSurfaceDarkHighContrast = Color(0xFFEFE0D3)
val inverseOnSurfaceDarkHighContrast = Color(0xFF000000)
val inversePrimaryDarkHighContrast = Color(0xFF3E2500)
val surfaceDimDarkHighContrast = Color(0xFF19120B)
val surfaceBrightDarkHighContrast = Color(0xFF40382E)
val surfaceContainerLowestDarkHighContrast = Color(0xFF130D06)
val surfaceContainerLowDarkHighContrast = Color(0xFF211A12)
val surfaceContainerDarkHighContrast = Color(0xFF261E16)
val surfaceContainerHighDarkHighContrast = Color(0xFF302920)
val surfaceContainerHighestDarkHighContrast = Color(0xFF3C332A)

View File

@ -0,0 +1,281 @@
package rs.ruffle.ui.theme
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
const val stronglyDeemphasizedAlpha = 0.6f
const val slightlyDeemphasizedAlpha = 0.87f
private val lightScheme = lightColorScheme(
primary = primaryLight,
onPrimary = onPrimaryLight,
primaryContainer = primaryContainerLight,
onPrimaryContainer = onPrimaryContainerLight,
secondary = secondaryLight,
onSecondary = onSecondaryLight,
secondaryContainer = secondaryContainerLight,
onSecondaryContainer = onSecondaryContainerLight,
tertiary = tertiaryLight,
onTertiary = onTertiaryLight,
tertiaryContainer = tertiaryContainerLight,
onTertiaryContainer = onTertiaryContainerLight,
error = errorLight,
onError = onErrorLight,
errorContainer = errorContainerLight,
onErrorContainer = onErrorContainerLight,
background = backgroundLight,
onBackground = onBackgroundLight,
surface = surfaceLight,
onSurface = onSurfaceLight,
surfaceVariant = surfaceVariantLight,
onSurfaceVariant = onSurfaceVariantLight,
outline = outlineLight,
outlineVariant = outlineVariantLight,
scrim = scrimLight,
inverseSurface = inverseSurfaceLight,
inverseOnSurface = inverseOnSurfaceLight,
inversePrimary = inversePrimaryLight,
surfaceDim = surfaceDimLight,
surfaceBright = surfaceBrightLight,
surfaceContainerLowest = surfaceContainerLowestLight,
surfaceContainerLow = surfaceContainerLowLight,
surfaceContainer = surfaceContainerLight,
surfaceContainerHigh = surfaceContainerHighLight,
surfaceContainerHighest = surfaceContainerHighestLight,
)
private val darkScheme = darkColorScheme(
primary = primaryDark,
onPrimary = onPrimaryDark,
primaryContainer = primaryContainerDark,
onPrimaryContainer = onPrimaryContainerDark,
secondary = secondaryDark,
onSecondary = onSecondaryDark,
secondaryContainer = secondaryContainerDark,
onSecondaryContainer = onSecondaryContainerDark,
tertiary = tertiaryDark,
onTertiary = onTertiaryDark,
tertiaryContainer = tertiaryContainerDark,
onTertiaryContainer = onTertiaryContainerDark,
error = errorDark,
onError = onErrorDark,
errorContainer = errorContainerDark,
onErrorContainer = onErrorContainerDark,
background = backgroundDark,
onBackground = onBackgroundDark,
surface = surfaceDark,
onSurface = onSurfaceDark,
surfaceVariant = surfaceVariantDark,
onSurfaceVariant = onSurfaceVariantDark,
outline = outlineDark,
outlineVariant = outlineVariantDark,
scrim = scrimDark,
inverseSurface = inverseSurfaceDark,
inverseOnSurface = inverseOnSurfaceDark,
inversePrimary = inversePrimaryDark,
surfaceDim = surfaceDimDark,
surfaceBright = surfaceBrightDark,
surfaceContainerLowest = surfaceContainerLowestDark,
surfaceContainerLow = surfaceContainerLowDark,
surfaceContainer = surfaceContainerDark,
surfaceContainerHigh = surfaceContainerHighDark,
surfaceContainerHighest = surfaceContainerHighestDark,
)
private val mediumContrastLightColorScheme = lightColorScheme(
primary = primaryLightMediumContrast,
onPrimary = onPrimaryLightMediumContrast,
primaryContainer = primaryContainerLightMediumContrast,
onPrimaryContainer = onPrimaryContainerLightMediumContrast,
secondary = secondaryLightMediumContrast,
onSecondary = onSecondaryLightMediumContrast,
secondaryContainer = secondaryContainerLightMediumContrast,
onSecondaryContainer = onSecondaryContainerLightMediumContrast,
tertiary = tertiaryLightMediumContrast,
onTertiary = onTertiaryLightMediumContrast,
tertiaryContainer = tertiaryContainerLightMediumContrast,
onTertiaryContainer = onTertiaryContainerLightMediumContrast,
error = errorLightMediumContrast,
onError = onErrorLightMediumContrast,
errorContainer = errorContainerLightMediumContrast,
onErrorContainer = onErrorContainerLightMediumContrast,
background = backgroundLightMediumContrast,
onBackground = onBackgroundLightMediumContrast,
surface = surfaceLightMediumContrast,
onSurface = onSurfaceLightMediumContrast,
surfaceVariant = surfaceVariantLightMediumContrast,
onSurfaceVariant = onSurfaceVariantLightMediumContrast,
outline = outlineLightMediumContrast,
outlineVariant = outlineVariantLightMediumContrast,
scrim = scrimLightMediumContrast,
inverseSurface = inverseSurfaceLightMediumContrast,
inverseOnSurface = inverseOnSurfaceLightMediumContrast,
inversePrimary = inversePrimaryLightMediumContrast,
surfaceDim = surfaceDimLightMediumContrast,
surfaceBright = surfaceBrightLightMediumContrast,
surfaceContainerLowest = surfaceContainerLowestLightMediumContrast,
surfaceContainerLow = surfaceContainerLowLightMediumContrast,
surfaceContainer = surfaceContainerLightMediumContrast,
surfaceContainerHigh = surfaceContainerHighLightMediumContrast,
surfaceContainerHighest = surfaceContainerHighestLightMediumContrast,
)
private val highContrastLightColorScheme = lightColorScheme(
primary = primaryLightHighContrast,
onPrimary = onPrimaryLightHighContrast,
primaryContainer = primaryContainerLightHighContrast,
onPrimaryContainer = onPrimaryContainerLightHighContrast,
secondary = secondaryLightHighContrast,
onSecondary = onSecondaryLightHighContrast,
secondaryContainer = secondaryContainerLightHighContrast,
onSecondaryContainer = onSecondaryContainerLightHighContrast,
tertiary = tertiaryLightHighContrast,
onTertiary = onTertiaryLightHighContrast,
tertiaryContainer = tertiaryContainerLightHighContrast,
onTertiaryContainer = onTertiaryContainerLightHighContrast,
error = errorLightHighContrast,
onError = onErrorLightHighContrast,
errorContainer = errorContainerLightHighContrast,
onErrorContainer = onErrorContainerLightHighContrast,
background = backgroundLightHighContrast,
onBackground = onBackgroundLightHighContrast,
surface = surfaceLightHighContrast,
onSurface = onSurfaceLightHighContrast,
surfaceVariant = surfaceVariantLightHighContrast,
onSurfaceVariant = onSurfaceVariantLightHighContrast,
outline = outlineLightHighContrast,
outlineVariant = outlineVariantLightHighContrast,
scrim = scrimLightHighContrast,
inverseSurface = inverseSurfaceLightHighContrast,
inverseOnSurface = inverseOnSurfaceLightHighContrast,
inversePrimary = inversePrimaryLightHighContrast,
surfaceDim = surfaceDimLightHighContrast,
surfaceBright = surfaceBrightLightHighContrast,
surfaceContainerLowest = surfaceContainerLowestLightHighContrast,
surfaceContainerLow = surfaceContainerLowLightHighContrast,
surfaceContainer = surfaceContainerLightHighContrast,
surfaceContainerHigh = surfaceContainerHighLightHighContrast,
surfaceContainerHighest = surfaceContainerHighestLightHighContrast,
)
private val mediumContrastDarkColorScheme = darkColorScheme(
primary = primaryDarkMediumContrast,
onPrimary = onPrimaryDarkMediumContrast,
primaryContainer = primaryContainerDarkMediumContrast,
onPrimaryContainer = onPrimaryContainerDarkMediumContrast,
secondary = secondaryDarkMediumContrast,
onSecondary = onSecondaryDarkMediumContrast,
secondaryContainer = secondaryContainerDarkMediumContrast,
onSecondaryContainer = onSecondaryContainerDarkMediumContrast,
tertiary = tertiaryDarkMediumContrast,
onTertiary = onTertiaryDarkMediumContrast,
tertiaryContainer = tertiaryContainerDarkMediumContrast,
onTertiaryContainer = onTertiaryContainerDarkMediumContrast,
error = errorDarkMediumContrast,
onError = onErrorDarkMediumContrast,
errorContainer = errorContainerDarkMediumContrast,
onErrorContainer = onErrorContainerDarkMediumContrast,
background = backgroundDarkMediumContrast,
onBackground = onBackgroundDarkMediumContrast,
surface = surfaceDarkMediumContrast,
onSurface = onSurfaceDarkMediumContrast,
surfaceVariant = surfaceVariantDarkMediumContrast,
onSurfaceVariant = onSurfaceVariantDarkMediumContrast,
outline = outlineDarkMediumContrast,
outlineVariant = outlineVariantDarkMediumContrast,
scrim = scrimDarkMediumContrast,
inverseSurface = inverseSurfaceDarkMediumContrast,
inverseOnSurface = inverseOnSurfaceDarkMediumContrast,
inversePrimary = inversePrimaryDarkMediumContrast,
surfaceDim = surfaceDimDarkMediumContrast,
surfaceBright = surfaceBrightDarkMediumContrast,
surfaceContainerLowest = surfaceContainerLowestDarkMediumContrast,
surfaceContainerLow = surfaceContainerLowDarkMediumContrast,
surfaceContainer = surfaceContainerDarkMediumContrast,
surfaceContainerHigh = surfaceContainerHighDarkMediumContrast,
surfaceContainerHighest = surfaceContainerHighestDarkMediumContrast,
)
private val highContrastDarkColorScheme = darkColorScheme(
primary = primaryDarkHighContrast,
onPrimary = onPrimaryDarkHighContrast,
primaryContainer = primaryContainerDarkHighContrast,
onPrimaryContainer = onPrimaryContainerDarkHighContrast,
secondary = secondaryDarkHighContrast,
onSecondary = onSecondaryDarkHighContrast,
secondaryContainer = secondaryContainerDarkHighContrast,
onSecondaryContainer = onSecondaryContainerDarkHighContrast,
tertiary = tertiaryDarkHighContrast,
onTertiary = onTertiaryDarkHighContrast,
tertiaryContainer = tertiaryContainerDarkHighContrast,
onTertiaryContainer = onTertiaryContainerDarkHighContrast,
error = errorDarkHighContrast,
onError = onErrorDarkHighContrast,
errorContainer = errorContainerDarkHighContrast,
onErrorContainer = onErrorContainerDarkHighContrast,
background = backgroundDarkHighContrast,
onBackground = onBackgroundDarkHighContrast,
surface = surfaceDarkHighContrast,
onSurface = onSurfaceDarkHighContrast,
surfaceVariant = surfaceVariantDarkHighContrast,
onSurfaceVariant = onSurfaceVariantDarkHighContrast,
outline = outlineDarkHighContrast,
outlineVariant = outlineVariantDarkHighContrast,
scrim = scrimDarkHighContrast,
inverseSurface = inverseSurfaceDarkHighContrast,
inverseOnSurface = inverseOnSurfaceDarkHighContrast,
inversePrimary = inversePrimaryDarkHighContrast,
surfaceDim = surfaceDimDarkHighContrast,
surfaceBright = surfaceBrightDarkHighContrast,
surfaceContainerLowest = surfaceContainerLowestDarkHighContrast,
surfaceContainerLow = surfaceContainerLowDarkHighContrast,
surfaceContainer = surfaceContainerDarkHighContrast,
surfaceContainerHigh = surfaceContainerHighDarkHighContrast,
surfaceContainerHighest = surfaceContainerHighestDarkHighContrast,
)
@Immutable
data class ColorFamily(
val color: Color,
val onColor: Color,
val colorContainer: Color,
val onColorContainer: Color
)
val unspecified_scheme = ColorFamily(
Color.Unspecified, Color.Unspecified, Color.Unspecified, Color.Unspecified
)
@Composable
fun RuffleTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = false,
content: @Composable() () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> darkScheme
else -> lightScheme
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}

View File

@ -0,0 +1,34 @@
package rs.ruffle.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with
val Typography = Typography(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
*/
)

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="328dp"
android:height="108dp"
android:viewportWidth="328"
android:viewportHeight="108">
<path
android:pathData="M37.85,61.25q-0.4,0 -0.8,0.05 -0.5,-0.25 -0.9,-0.5 -0.3,-0.1 -0.55,-0.3l-0.6,-0.6 -4.25,-6.45 -1.5,11.25h3.45q0.75,-0.1 1.5,-0.35 0.85,-0.25 1.65,-0.75 0.55,-0.35 1.05,-0.8 0.5,-0.45 0.95,-1 0.5,-0.5 0.75,-1.2 -0.05,0.05 -0.15,0.1 -0.1,0.15 -0.25,0.25l-0.1,0.2q-0.15,0.05 -0.25,0.1m58.3,0q-0.9,0 -1.45,-0.7 -0.3,-0.3 -0.55,-0.6l-0.05,-0.05v-0.05l-4.25,-6.4 -1.5,11.25h3.45q0.75,-0.1 1.5,-0.35 0.85,-0.25 1.6,-0.75 0.75,-0.5 1.4,-1.1 0.45,-0.35 0.75,-0.85 0.35,-0.5 0.65,-1.05l-0.45,0.55q-0.5,0.15 -1.1,0.1m19.7,3.25h3.45q0.75,-0.1 1.5,-0.35 0.25,-0.05 0.45,-0.15 0.35,-0.15 0.65,-0.3l0.5,-0.3q0.25,-0.15 0.5,-0.35 0.45,-0.35 0.9,-0.75 0.45,-0.35 0.75,-0.85l0.1,-0.1q0.1,-0.2 0.2,-0.35 0.2,-0.3 0.35,-0.6l-0.3,0.4 -0.15,0.15q-0.5,0.15 -1.1,0.1 -0.25,0 -0.4,-0.05 -0.5,-0.15 -0.8,-0.4 -0.15,-0.1 -0.25,-0.25 -0.3,-0.3 -0.55,-0.6l-0.05,-0.05v-0.05l-4.25,-6.4 -1.5,11.25m52.6,0.2q-3.1,-0.25 -5.7,-0.75 -5.6,-1.05 -8.9,-3.1 -0.75,-0.5 -1.4,-1 -3.15,-2.55 -3.5,-6.4l-1.5,11.25h21m33.4,-5.65q-2.3,-2.35 -2.6,-5.6l-1.5,11.25h21q-11.25,-0.95 -16,-4.85 -0.5,-0.4 -0.9,-0.8m55.15,2.5q-2.55,-0.25 -3.25,-1.8l-4.2,-6.3 -1.5,11.25h3.45q0.6,-0.1 1.2,-0.3 0.4,-0.1 0.75,-0.2 0.35,-0.15 0.65,-0.3 0.7,-0.35 1.35,-0.8 0.75,-0.55 1.3,-1.25 0.1,-0.15 0.25,-0.3m23.95,-8.05l-1.5,11.2h21q-0.3,-0.05 -0.6,-0.05 -10.8,-1 -15.4,-4.8 -3.15,-2.55 -3.5,-6.35z"
android:fillColor="#966214"/>
<path
android:pathData="M40.65,47.25H51.8L49.75,61.7h16.5l2.3,-16.25h-0.05l0.8,-5.7q0.4,-2.45 -1,-4.2 -0.35,-0.4 -0.75,-0.8 -0.25,-0.25 -0.55,-0.5 -0.2,-0.2 -0.45,-0.35 -1.95,-1.4 -4.5,-1.4H34.3q-1.35,0 -2.6,0.45 -1.65,0.55 -3.15,1.8Q25.8,37 25.3,40l-1.65,12h0.05v0.3l5.85,1.15h-9.5q-0.5,0.05 -1,0.15 -0.5,0.15 -1,0.35 -0.5,0.2 -0.95,0.45 -0.5,0.3 -0.95,0.7 -0.45,0.35 -0.85,0.8 -0.35,0.4 -0.65,0.85 -0.3,0.45 -0.5,0.9 -0.15,0.45 -0.3,0.95L8,100.2h16.25l5,-35.5 1.5,-11.25L35,59.9l0.6,0.6q0.25,0.2 0.55,0.3 0.4,0.25 0.9,0.5 0.4,-0.05 0.8,-0.05 0.1,-0.05 0.25,-0.1l0.1,-0.2q0.15,-0.1 0.25,-0.25 0.1,-0.05 0.15,-0.1l0.3,-1.05 1.75,-12.3m86.8,-2h-0.05l0.15,-1.25h-0.05l1.65,-11.7H112.9l-2.65,19.5h0.05v0.2l-0.05,0.1h0.05l5.8,1.15h-9.45q-0.5,0.05 -1,0.15 -0.5,0.15 -1,0.35 -0.15,0.05 -0.3,0.15 -0.3,0.1 -0.55,0.25 -0.05,0 -0.1,0.05 -0.5,0.3 -1,0.65 -0.4,0.35 -0.7,0.7 -0.55,0.7 -0.95,1.45 -0.35,0.65 -0.55,1.4 -0.15,0.7 -0.25,1.4v0.05q-0.15,1.05 -0.35,2.05l-1.2,8.75v0.1l-2.1,14.7H85.35L87.6,69.9h0.05l0.7,-5.2 1.5,-11.25 4.25,6.4v0.05l0.05,0.05q0.25,0.3 0.55,0.6 0.55,0.7 1.45,0.7 0.6,0.05 1.1,-0.1l0.45,-0.55 0.3,-1.05 1.3,-9.05h-0.05l0.7,-5.05h-0.05l0.15,-1.25H100l1.65,-11.7H85.4L82.75,52h0.05l-0.05,0.3 5.85,1.15h-9.45q-0.5,0.05 -1,0.15 -0.5,0.15 -1,0.35 -0.5,0.2 -0.95,0.45 -0.5,0.3 -1,0.65 -0.4,0.4 -0.8,0.85 -0.25,0.3 -0.55,0.65 -0.05,0.1 -0.15,0.2 -0.25,0.45 -0.4,0.9 -0.2,0.45 -0.3,0.95 -0.1,0.65 -0.2,1.25 -0.2,1.15 -0.4,2.25l-4.3,30.6q-0.25,3 1.75,5.25 1.6,1.8 4,2.15 0.6,0.1 1.25,0.1h27.35q3.25,0 6,-2.25 0.35,-0.35 0.7,-0.55l0.3,-0.2q2,-2 2.25,-4.5l1.65,-11.6q0.05,-0.05 0.1,-0.05l1.65,-11.35h0.05l0.7,-5.2 1.5,-11.25 4.25,6.4v0.05l0.05,0.05q0.25,0.3 0.55,0.6 0.1,0.15 0.25,0.25 0.3,0.25 0.8,0.4 0.15,0.05 0.4,0.05 0.6,0.05 1.1,-0.1l0.15,-0.15 0.3,-0.4 0.3,-1.05 1.3,-9.05h-0.05l0.7,-5.05m61.8,-35Q187,8 183.75,8H156q-3,0 -5.75,2.25 -1.3,0.95 -2.05,2.1 -0.45,0.6 -0.7,1.2 -0.2,0.5 -0.35,1 -0.1,0.45 -0.15,0.95l-4.15,29.95h-0.05l-0.7,5.2h-0.05l-0.2,1.35h0.05l-0.05,0.3 5.85,1.15h-9.45q-2.1,0.05 -3.95,1.6 -1.9,1.55 -2.25,3.55l-0.5,3.5h-0.05l-5.3,38.1h16.25l5,-35.5 1.5,-11.25q0.35,3.85 3.5,6.4 0.65,0.5 1.4,1 3.3,2.05 8.9,3.1 2.6,0.5 5.7,0.75l1.75,-11.25H158l0.4,-2.95h-0.05l0.7,-5.05H159q0.1,-0.9 0.3,-1.9 0.1,-0.75 0.2,-1.6 0.85,-5.9 2.15,-14.9 0,-0.15 0.05,-0.25l0.1,-0.9q0.2,-1.55 0.45,-3.15h11.25l-3.1,20.8h16.5L191,15.5q0.15,-1.7 -0.4,-3.15 -0.5,-1.1 -1.35,-2.1m50.3,0Q237.3,8 234.05,8H206.3q-2.3,0 -4.45,1.35 -0.65,0.35 -1.3,0.9 -1.3,0.95 -2.05,2.1 -0.45,0.6 -0.7,1.2 -0.4,0.9 -0.5,1.95l-4.15,29.95h-0.05l-0.7,5.2h-0.05l-0.2,1.35h0.05l-0.05,0.3 5.85,1.15h-9.45q-2.1,0.05 -3.95,1.6 -1.9,1.55 -2.25,3.55l-0.5,3.5h-0.05l-1.2,8.75v0.1l-4.1,29.25h16.25l5,-35.5 1.5,-11.25q0.3,3.25 2.6,5.6 0.4,0.4 0.9,0.8 4.75,3.9 16,4.85l1.75,-11.25h-12.2l0.4,-2.95h-0.05l0.7,-5.05h-0.05q0.15,-0.9 0.3,-1.9 0.1,-0.75 0.25,-1.6 0.15,-1.25 0.35,-2.65v-0.05q0.95,-6.7 2.35,-16.5h11.25l-3.1,20.8h16.5l4.1,-28.05q0.15,-1.7 -0.4,-3.15 -0.5,-1.1 -1.35,-2.1M259.5,45.5v-0.05L264.85,8H248.6l-6.15,44.3 5.85,1.15h-9.45q-0.5,0.05 -1,0.15 -0.5,0.15 -1,0.35 -0.5,0.2 -0.95,0.45 -0.5,0.3 -1,0.65 -0.4,0.4 -0.8,0.85 -0.35,0.4 -0.7,0.85 -0.25,0.45 -0.45,0.9 -0.15,0.45 -0.3,0.95l-5.85,41.6h16.25l5,-35.5 1.5,-11.25 4.2,6.3q0.7,1.55 3.25,1.8l0.05,-0.1q0.25,-0.4 0.35,-0.85l0.3,-1.05 1.8,-14.05m25.6,-15.75q-0.65,0 -1.3,0.1 -2.5,0.35 -4.7,2.15 -2.75,2.25 -3.25,5.25l-1.95,14.7V52l-0.05,0.3 5.85,1.15h-9.45q-1.9,0.05 -3.6,1.35 -0.2,0.1 -0.35,0.25 -1.9,1.55 -2.25,3.55l-4.85,34.1q-0.25,3 1.75,5.25 1.25,1.4 3,1.95 1.05,0.3 2.25,0.3h27.75q3.25,0 6,-2.25 2.75,-2 3.25,-5l2.75,-18.5h-16.5l-1.75,11h-11.25l2.1,-14.75h0.05l0.85,-6 1.5,-11.2q0.35,3.8 3.5,6.35 4.6,3.8 15.4,4.8 0.3,0 0.6,0.05h15.75l2.75,-19.25h-0.05l1.15,-8.2q0.5,-3 -1.75,-5.25 -1.25,-1.25 -3,-1.75 -1,-0.5 -2.25,-0.5H285.1m5.95,15.7h-0.1l0.15,-0.95h11.45l-1.25,8.95H290l0.4,-2.95h-0.05l0.7,-5.05z"
android:fillColor="#FFAD33"/>
</vector>

View File

@ -5,8 +5,8 @@
android:viewportHeight="108">
<path
android:pathData="M37.85,61.25q-0.4,0 -0.8,0.05 -0.5,-0.25 -0.9,-0.5 -0.3,-0.1 -0.55,-0.3l-0.6,-0.6 -4.25,-6.45 -1.5,11.25h3.45q0.75,-0.1 1.5,-0.35 0.85,-0.25 1.65,-0.75 0.55,-0.35 1.05,-0.8 0.5,-0.45 0.95,-1 0.5,-0.5 0.75,-1.2 -0.05,0.05 -0.15,0.1 -0.1,0.15 -0.25,0.25l-0.1,0.2q-0.15,0.05 -0.25,0.1m58.3,0q-0.9,0 -1.45,-0.7 -0.3,-0.3 -0.55,-0.6l-0.05,-0.05v-0.05l-4.25,-6.4 -1.5,11.25h3.45q0.75,-0.1 1.5,-0.35 0.85,-0.25 1.6,-0.75 0.75,-0.5 1.4,-1.1 0.45,-0.35 0.75,-0.85 0.35,-0.5 0.65,-1.05l-0.45,0.55q-0.5,0.15 -1.1,0.1m19.7,3.25h3.45q0.75,-0.1 1.5,-0.35 0.25,-0.05 0.45,-0.15 0.35,-0.15 0.65,-0.3l0.5,-0.3q0.25,-0.15 0.5,-0.35 0.45,-0.35 0.9,-0.75 0.45,-0.35 0.75,-0.85l0.1,-0.1q0.1,-0.2 0.2,-0.35 0.2,-0.3 0.35,-0.6l-0.3,0.4 -0.15,0.15q-0.5,0.15 -1.1,0.1 -0.25,0 -0.4,-0.05 -0.5,-0.15 -0.8,-0.4 -0.15,-0.1 -0.25,-0.25 -0.3,-0.3 -0.55,-0.6l-0.05,-0.05v-0.05l-4.25,-6.4 -1.5,11.25m52.6,0.2q-3.1,-0.25 -5.7,-0.75 -5.6,-1.05 -8.9,-3.1 -0.75,-0.5 -1.4,-1 -3.15,-2.55 -3.5,-6.4l-1.5,11.25h21m33.4,-5.65q-2.3,-2.35 -2.6,-5.6l-1.5,11.25h21q-11.25,-0.95 -16,-4.85 -0.5,-0.4 -0.9,-0.8m55.15,2.5q-2.55,-0.25 -3.25,-1.8l-4.2,-6.3 -1.5,11.25h3.45q0.6,-0.1 1.2,-0.3 0.4,-0.1 0.75,-0.2 0.35,-0.15 0.65,-0.3 0.7,-0.35 1.35,-0.8 0.75,-0.55 1.3,-1.25 0.1,-0.15 0.25,-0.3m23.95,-8.05l-1.5,11.2h21q-0.3,-0.05 -0.6,-0.05 -10.8,-1 -15.4,-4.8 -3.15,-2.55 -3.5,-6.35z"
android:fillColor="#966214"/>
android:fillColor="#2c4980"/>
<path
android:pathData="M40.65,47.25H51.8L49.75,61.7h16.5l2.3,-16.25h-0.05l0.8,-5.7q0.4,-2.45 -1,-4.2 -0.35,-0.4 -0.75,-0.8 -0.25,-0.25 -0.55,-0.5 -0.2,-0.2 -0.45,-0.35 -1.95,-1.4 -4.5,-1.4H34.3q-1.35,0 -2.6,0.45 -1.65,0.55 -3.15,1.8Q25.8,37 25.3,40l-1.65,12h0.05v0.3l5.85,1.15h-9.5q-0.5,0.05 -1,0.15 -0.5,0.15 -1,0.35 -0.5,0.2 -0.95,0.45 -0.5,0.3 -0.95,0.7 -0.45,0.35 -0.85,0.8 -0.35,0.4 -0.65,0.85 -0.3,0.45 -0.5,0.9 -0.15,0.45 -0.3,0.95L8,100.2h16.25l5,-35.5 1.5,-11.25L35,59.9l0.6,0.6q0.25,0.2 0.55,0.3 0.4,0.25 0.9,0.5 0.4,-0.05 0.8,-0.05 0.1,-0.05 0.25,-0.1l0.1,-0.2q0.15,-0.1 0.25,-0.25 0.1,-0.05 0.15,-0.1l0.3,-1.05 1.75,-12.3m86.8,-2h-0.05l0.15,-1.25h-0.05l1.65,-11.7H112.9l-2.65,19.5h0.05v0.2l-0.05,0.1h0.05l5.8,1.15h-9.45q-0.5,0.05 -1,0.15 -0.5,0.15 -1,0.35 -0.15,0.05 -0.3,0.15 -0.3,0.1 -0.55,0.25 -0.05,0 -0.1,0.05 -0.5,0.3 -1,0.65 -0.4,0.35 -0.7,0.7 -0.55,0.7 -0.95,1.45 -0.35,0.65 -0.55,1.4 -0.15,0.7 -0.25,1.4v0.05q-0.15,1.05 -0.35,2.05l-1.2,8.75v0.1l-2.1,14.7H85.35L87.6,69.9h0.05l0.7,-5.2 1.5,-11.25 4.25,6.4v0.05l0.05,0.05q0.25,0.3 0.55,0.6 0.55,0.7 1.45,0.7 0.6,0.05 1.1,-0.1l0.45,-0.55 0.3,-1.05 1.3,-9.05h-0.05l0.7,-5.05h-0.05l0.15,-1.25H100l1.65,-11.7H85.4L82.75,52h0.05l-0.05,0.3 5.85,1.15h-9.45q-0.5,0.05 -1,0.15 -0.5,0.15 -1,0.35 -0.5,0.2 -0.95,0.45 -0.5,0.3 -1,0.65 -0.4,0.4 -0.8,0.85 -0.25,0.3 -0.55,0.65 -0.05,0.1 -0.15,0.2 -0.25,0.45 -0.4,0.9 -0.2,0.45 -0.3,0.95 -0.1,0.65 -0.2,1.25 -0.2,1.15 -0.4,2.25l-4.3,30.6q-0.25,3 1.75,5.25 1.6,1.8 4,2.15 0.6,0.1 1.25,0.1h27.35q3.25,0 6,-2.25 0.35,-0.35 0.7,-0.55l0.3,-0.2q2,-2 2.25,-4.5l1.65,-11.6q0.05,-0.05 0.1,-0.05l1.65,-11.35h0.05l0.7,-5.2 1.5,-11.25 4.25,6.4v0.05l0.05,0.05q0.25,0.3 0.55,0.6 0.1,0.15 0.25,0.25 0.3,0.25 0.8,0.4 0.15,0.05 0.4,0.05 0.6,0.05 1.1,-0.1l0.15,-0.15 0.3,-0.4 0.3,-1.05 1.3,-9.05h-0.05l0.7,-5.05m61.8,-35Q187,8 183.75,8H156q-3,0 -5.75,2.25 -1.3,0.95 -2.05,2.1 -0.45,0.6 -0.7,1.2 -0.2,0.5 -0.35,1 -0.1,0.45 -0.15,0.95l-4.15,29.95h-0.05l-0.7,5.2h-0.05l-0.2,1.35h0.05l-0.05,0.3 5.85,1.15h-9.45q-2.1,0.05 -3.95,1.6 -1.9,1.55 -2.25,3.55l-0.5,3.5h-0.05l-5.3,38.1h16.25l5,-35.5 1.5,-11.25q0.35,3.85 3.5,6.4 0.65,0.5 1.4,1 3.3,2.05 8.9,3.1 2.6,0.5 5.7,0.75l1.75,-11.25H158l0.4,-2.95h-0.05l0.7,-5.05H159q0.1,-0.9 0.3,-1.9 0.1,-0.75 0.2,-1.6 0.85,-5.9 2.15,-14.9 0,-0.15 0.05,-0.25l0.1,-0.9q0.2,-1.55 0.45,-3.15h11.25l-3.1,20.8h16.5L191,15.5q0.15,-1.7 -0.4,-3.15 -0.5,-1.1 -1.35,-2.1m50.3,0Q237.3,8 234.05,8H206.3q-2.3,0 -4.45,1.35 -0.65,0.35 -1.3,0.9 -1.3,0.95 -2.05,2.1 -0.45,0.6 -0.7,1.2 -0.4,0.9 -0.5,1.95l-4.15,29.95h-0.05l-0.7,5.2h-0.05l-0.2,1.35h0.05l-0.05,0.3 5.85,1.15h-9.45q-2.1,0.05 -3.95,1.6 -1.9,1.55 -2.25,3.55l-0.5,3.5h-0.05l-1.2,8.75v0.1l-4.1,29.25h16.25l5,-35.5 1.5,-11.25q0.3,3.25 2.6,5.6 0.4,0.4 0.9,0.8 4.75,3.9 16,4.85l1.75,-11.25h-12.2l0.4,-2.95h-0.05l0.7,-5.05h-0.05q0.15,-0.9 0.3,-1.9 0.1,-0.75 0.25,-1.6 0.15,-1.25 0.35,-2.65v-0.05q0.95,-6.7 2.35,-16.5h11.25l-3.1,20.8h16.5l4.1,-28.05q0.15,-1.7 -0.4,-3.15 -0.5,-1.1 -1.35,-2.1M259.5,45.5v-0.05L264.85,8H248.6l-6.15,44.3 5.85,1.15h-9.45q-0.5,0.05 -1,0.15 -0.5,0.15 -1,0.35 -0.5,0.2 -0.95,0.45 -0.5,0.3 -1,0.65 -0.4,0.4 -0.8,0.85 -0.35,0.4 -0.7,0.85 -0.25,0.45 -0.45,0.9 -0.15,0.45 -0.3,0.95l-5.85,41.6h16.25l5,-35.5 1.5,-11.25 4.2,6.3q0.7,1.55 3.25,1.8l0.05,-0.1q0.25,-0.4 0.35,-0.85l0.3,-1.05 1.8,-14.05m25.6,-15.75q-0.65,0 -1.3,0.1 -2.5,0.35 -4.7,2.15 -2.75,2.25 -3.25,5.25l-1.95,14.7V52l-0.05,0.3 5.85,1.15h-9.45q-1.9,0.05 -3.6,1.35 -0.2,0.1 -0.35,0.25 -1.9,1.55 -2.25,3.55l-4.85,34.1q-0.25,3 1.75,5.25 1.25,1.4 3,1.95 1.05,0.3 2.25,0.3h27.75q3.25,0 6,-2.25 2.75,-2 3.25,-5l2.75,-18.5h-16.5l-1.75,11h-11.25l2.1,-14.75h0.05l0.85,-6 1.5,-11.2q0.35,3.8 3.5,6.35 4.6,3.8 15.4,4.8 0.3,0 0.6,0.05h15.75l2.75,-19.25h-0.05l1.15,-8.2q0.5,-3 -1.75,-5.25 -1.25,-1.25 -3,-1.75 -1,-0.5 -2.25,-0.5H285.1m5.95,15.7h-0.1l0.15,-0.95h11.45l-1.25,8.95H290l0.4,-2.95h-0.05l0.7,-5.05z"
android:fillColor="#FFAD33"/>
android:fillColor="#37528c"/>
</vector>

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFAD33</color>
<color name="purple_500">#FFAD33</color>
<color name="purple_700">#FFAD33</color>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>

View File

@ -0,0 +1,9 @@
<resources>
<string name="app_name">Ruffle</string>
<string name="logo_description">Ruffle Logo</string>
<string name="or">or</string>
<string name="open_url">Open URL</string>
<string name="select_a_swf">Select a SWF</string>
<string name="url">URL</string>
<string name="work_in_progress_warning">This app is a work in progress. Some functionality may not be implemented yet!</string>
</resources>

View File

@ -0,0 +1,5 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.Ruffle" parent="Theme.AppCompat.Light.DarkActionBar">
</style>
</resources>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample backup rules file; uncomment and customize as necessary.
See https://developer.android.com/guide/topics/data/autobackup
for details.
Note: This file is ignored for devices older that API 31
See https://developer.android.com/about/versions/12/backup-restore
-->
<full-backup-content>
<!--
<include domain="sharedpref" path="."/>
<exclude domain="sharedpref" path="device.xml"/>
-->
</full-backup-content>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample data extraction rules file; uncomment and customize as necessary.
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
for details.
-->
<data-extraction-rules>
<cloud-backup>
<!-- TODO: Use <include> and <exclude> to control what is backed up.
<include .../>
<exclude .../>
-->
</cloud-backup>
<!--
<device-transfer>
<include .../>
<exclude .../>
</device-transfer>
-->
</data-extraction-rules>

View File

@ -0,0 +1,17 @@
package rs.ruffle
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}

6
build.gradle.kts Normal file
View File

@ -0,0 +1,6 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
alias(libs.plugins.androidApplication) apply false
alias(libs.plugins.jetbrainsKotlinAndroid) apply false
alias(libs.plugins.cargoNdkAndroid) apply false
}

View File

@ -8,15 +8,16 @@
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# This option should only be used with decoupled projects. For more details, visit
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
android.nonFinalResIds=false
android.nonTransitiveRClass=true

45
gradle/libs.versions.toml Normal file
View File

@ -0,0 +1,45 @@
[versions]
agp = "8.3.0"
kotlin = "1.9.0"
coreKtx = "1.12.0"
junit = "4.13.2"
junitVersion = "1.1.5"
espressoCore = "3.5.1"
lifecycleRuntimeKtx = "2.7.0"
activityCompose = "1.8.2"
composeBom = "2024.02.02"
lifecycleViewmodelCompose = "2.7.0"
navigationRuntimeKtx = "2.7.7"
navigationCompose = "2.7.7"
gamesActivity = "2.0.2" # Needs to be in sync with android-activity crate
constraintlayout = "2.1.4"
appcompat = "1.6.1"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelCompose" }
androidx-navigation-runtime-ktx = { group = "androidx.navigation", name = "navigation-runtime-ktx", version.ref = "navigationRuntimeKtx" }
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" }
androidx-games-activity = { group = "androidx.games", name = "games-activity", version.ref = "gamesActivity" }
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
jetbrainsKotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
cargoNdkAndroid = { id = "com.github.willir.rust.cargo-ndk-android", version = "0.3.4" }

View File

@ -1,6 +1,6 @@
#Tue Jan 16 22:20:46 CET 2024
#Tue Mar 12 12:21:42 CET 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

View File

1
native/.gitignore vendored
View File

@ -1 +0,0 @@
target

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -1,8 +1,14 @@
pluginManagement {
repositories {
gradlePluginPortal()
google()
google {
content {
includeGroupByRegex("com\\.android.*")
includeGroupByRegex("com\\.google.*")
includeGroupByRegex("androidx.*")
}
}
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
@ -12,5 +18,7 @@ dependencyResolutionManagement {
mavenCentral()
}
}
rootProject.name = "Ruffle"
include ':ruffle'
include(":app")