diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2f9961a..83b6cc5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -130,6 +130,43 @@ jobs: - name: Build iOS (release, no codesign) run: flutter build ios --release --no-codesign + build-android: + name: Build Android + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: "17" + + - uses: subosito/flutter-action@v2 + with: + flutter-version: ${{ env.FLUTTER_VERSION }} + cache: true + + - name: Cache Gradle + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: gradle-${{ runner.os }}-${{ hashFiles('android/**/*.gradle*', 'android/gradle/wrapper/gradle-wrapper.properties') }} + restore-keys: gradle-${{ runner.os }}- + + - name: Install dependencies + run: flutter pub get + + - name: Run code generation + run: dart run build_runner build --delete-conflicting-outputs + + # No signing secrets on PRs: the release build falls back to the debug + # keystore (see android/app/build.gradle.kts), so this validates the + # whole release pipeline short of the Play upload. + - name: Build Android app bundle + run: flutter build appbundle --release + build-web: name: Build Web runs-on: ubuntu-latest diff --git a/.github/workflows/playstore.yml b/.github/workflows/playstore.yml new file mode 100644 index 0000000..1e69ec8 --- /dev/null +++ b/.github/workflows/playstore.yml @@ -0,0 +1,141 @@ +name: Deploy to Play Store + +on: + push: + branches: [main] + paths: + - "lib/**" + - "android/**" + - "pubspec.yaml" + - "pubspec.lock" + - ".github/workflows/playstore.yml" + workflow_dispatch: + inputs: + track: + description: "Play track to upload to" + type: choice + options: [internal, alpha] + default: internal + release_status: + description: "Release status (a brand-new app must use draft until its first release is rolled out in the Play Console)" + type: choice + options: [draft, completed] + default: draft + +# Serialize Play runs. The version code is max(track version codes) + 1, so two +# concurrent runs compute the SAME code and the second upload is rejected as a +# duplicate. Queue instead of racing, and never cancel an in-flight upload. +concurrency: + group: playstore-deploy + cancel-in-progress: false + +env: + FLUTTER_VERSION: "3.41.4" + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v6 + + # Without Play credentials the workflow still builds and attaches the signed + # AAB (the very first upload of a new app must happen manually in the Play + # Console anyway - the publisher API can't create it). + - name: Check Play credentials + id: play + env: + PLAY_SERVICE_ACCOUNT_JSON: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }} + run: | + if [ -n "$PLAY_SERVICE_ACCOUNT_JSON" ]; then + echo "available=true" >> "$GITHUB_OUTPUT" + else + echo "available=false" >> "$GITHUB_OUTPUT" + echo "::notice::PLAY_SERVICE_ACCOUNT_JSON is not set - building the signed AAB as an artifact and skipping the Play upload." + fi + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.3" + + - name: Calculate build number + id: build_number + env: + PLAY_SERVICE_ACCOUNT_JSON: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }} + run: | + VERSION=$(grep '^version:' pubspec.yaml | sed 's/version: //' | sed 's/+.*//') + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" + if [ "${{ steps.play.outputs.available }}" = "true" ]; then + printf '%s' "$PLAY_SERVICE_ACCOUNT_JSON" > "$RUNNER_TEMP/play-service-account.json" + export PLAY_JSON_KEY_PATH="$RUNNER_TEMP/play-service-account.json" + cd android + gem install fastlane + fastlane next_build_number + else + echo "build_number=${{ github.run_number }}" >> "$GITHUB_OUTPUT" + fi + + - name: Set up Java + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: "17" + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: ${{ env.FLUTTER_VERSION }} + cache: true + + - name: Install dependencies + run: flutter pub get + + - name: Run code generation + run: dart run build_runner build --delete-conflicting-outputs + + - name: Configure signing + env: + ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} + ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} + ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} + ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} + run: | + printf '%s' "$ANDROID_KEYSTORE_BASE64" | base64 --decode > android/upload-keystore.jks + cat > android/key.properties << EOF + storePassword=$ANDROID_KEYSTORE_PASSWORD + keyPassword=$ANDROID_KEY_PASSWORD + keyAlias=$ANDROID_KEY_ALIAS + storeFile=../upload-keystore.jks + EOF + + - name: Build AAB + run: | + flutter build appbundle \ + --build-number=${{ steps.build_number.outputs.build_number }} \ + --build-name=${{ steps.build_number.outputs.version }} + + - name: Upload AAB artifact + uses: actions/upload-artifact@v4 + with: + name: nullfeed-${{ steps.build_number.outputs.version }}-${{ steps.build_number.outputs.build_number }}-aab + path: build/app/outputs/bundle/release/app-release.aab + if-no-files-found: error + + - name: Upload to Play Store + if: steps.play.outputs.available == 'true' + env: + PLAY_SERVICE_ACCOUNT_JSON: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }} + PLAY_TRACK: ${{ inputs.track || 'internal' }} + PLAY_RELEASE_STATUS: ${{ inputs.release_status || 'draft' }} + run: | + printf '%s' "$PLAY_SERVICE_ACCOUNT_JSON" > "$RUNNER_TEMP/play-service-account.json" + export PLAY_JSON_KEY_PATH="$RUNNER_TEMP/play-service-account.json" + cd android + fastlane beta + + - name: Cleanup signing material + if: always() + run: | + rm -f android/key.properties android/upload-keystore.jks "$RUNNER_TEMP/play-service-account.json" diff --git a/.github/workflows/storelisting.yml b/.github/workflows/storelisting.yml new file mode 100644 index 0000000..d33bd49 --- /dev/null +++ b/.github/workflows/storelisting.yml @@ -0,0 +1,57 @@ +name: Sync Store Listing + +# Play listing copy/graphics live in-repo (android/fastlane/metadata); merging a +# change to them pushes the listing to the Play Console. No binaries here. +on: + push: + branches: [main] + paths: + - "android/fastlane/metadata/**" + - ".github/workflows/storelisting.yml" + workflow_dispatch: + +concurrency: + group: storelisting + cancel-in-progress: false + +jobs: + play-listing: + runs-on: ubuntu-latest + defaults: + run: + working-directory: android + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Check Play credentials + id: play + env: + PLAY_SERVICE_ACCOUNT_JSON: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }} + run: | + if [ -n "$PLAY_SERVICE_ACCOUNT_JSON" ]; then + echo "available=true" >> "$GITHUB_OUTPUT" + else + echo "available=false" >> "$GITHUB_OUTPUT" + echo "::notice::PLAY_SERVICE_ACCOUNT_JSON is not set - skipping the Play listing sync." + fi + + - name: Set up Ruby + if: steps.play.outputs.available == 'true' + uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.3" + + - name: Push listing + if: steps.play.outputs.available == 'true' + env: + PLAY_SERVICE_ACCOUNT_JSON: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }} + run: | + printf '%s' "$PLAY_SERVICE_ACCOUNT_JSON" > "$RUNNER_TEMP/play-service-account.json" + export PLAY_JSON_KEY_PATH="$RUNNER_TEMP/play-service-account.json" + gem install fastlane + fastlane listing + + - name: Cleanup credentials + if: always() + run: rm -f "$RUNNER_TEMP/play-service-account.json" diff --git a/.metadata b/.metadata index ab7697c..05a325e 100644 --- a/.metadata +++ b/.metadata @@ -15,7 +15,7 @@ migration: - platform: root create_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a base_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a - - platform: web + - platform: android create_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a base_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..be3943c --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,14 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java +.cxx/ + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts new file mode 100644 index 0000000..d7d3b8a --- /dev/null +++ b/android/app/build.gradle.kts @@ -0,0 +1,65 @@ +import java.io.FileInputStream +import java.util.Properties + +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +// CI decodes the upload keystore and writes android/key.properties; local builds +// without it fall back to debug signing so `flutter run --release` still works. +val keystorePropertiesFile = rootProject.file("key.properties") +val keystoreProperties = Properties() +if (keystorePropertiesFile.exists()) { + FileInputStream(keystorePropertiesFile).use { keystoreProperties.load(it) } +} + +android { + namespace = "codes.julian.nullfeed" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17.toString() + } + + defaultConfig { + applicationId = "codes.julian.nullfeed" + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + signingConfigs { + if (keystorePropertiesFile.exists()) { + create("release") { + keyAlias = keystoreProperties["keyAlias"] as String + keyPassword = keystoreProperties["keyPassword"] as String + storeFile = file(keystoreProperties["storeFile"] as String) + storePassword = keystoreProperties["storePassword"] as String + } + } + } + + buildTypes { + release { + signingConfig = if (keystorePropertiesFile.exists()) { + signingConfigs.getByName("release") + } else { + signingConfigs.getByName("debug") + } + } + } +} + +flutter { + source = "../.." +} diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..80ba1eb --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/kotlin/codes/julian/nullfeed/MainActivity.kt b/android/app/src/main/kotlin/codes/julian/nullfeed/MainActivity.kt new file mode 100644 index 0000000..29c7fef --- /dev/null +++ b/android/app/src/main/kotlin/codes/julian/nullfeed/MainActivity.kt @@ -0,0 +1,5 @@ +package codes.julian.nullfeed + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..57992db --- /dev/null +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..57992db --- /dev/null +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..80b730f --- /dev/null +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..7e6a67c Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..6775af2 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..46436d2 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..0cfc056 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..8f89fae Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..0a5a9a9 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..f5b74fe Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..ea35289 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..a56e50e Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..400579e Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..ae0870f --- /dev/null +++ b/android/app/src/main/res/values/colors.xml @@ -0,0 +1,5 @@ + + + + #07090D + diff --git a/android/app/src/main/res/values/ic_launcher_background.xml b/android/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..c041278 --- /dev/null +++ b/android/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #7C4DFF + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/build.gradle.kts b/android/build.gradle.kts new file mode 100644 index 0000000..dbee657 --- /dev/null +++ b/android/build.gradle.kts @@ -0,0 +1,24 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = + rootProject.layout.buildDirectory + .dir("../../build") + .get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/android/fastlane/Fastfile b/android/fastlane/Fastfile new file mode 100644 index 0000000..fd9c289 --- /dev/null +++ b/android/fastlane/Fastfile @@ -0,0 +1,65 @@ +default_platform(:android) + +PACKAGE_NAME = "codes.julian.nullfeed" + +platform :android do + desc "Get next version code from Play (max across all tracks + 1)" + lane :next_build_number do + codes = [] + %w[internal alpha beta production].each do |track| + begin + codes += google_play_track_version_codes( + package_name: PACKAGE_NAME, + track: track, + json_key: ENV["PLAY_JSON_KEY_PATH"], + ) + rescue => e + UI.message("Track #{track}: #{e.message.to_s.lines.first&.strip} (skipping)") + end + end + next_build = (codes.max || 0) + 1 + + UI.message("Play version codes: #{codes.inspect}, next: #{next_build}") + File.write(ENV['GITHUB_OUTPUT'], "build_number=#{next_build}\n", mode: "a") if ENV['GITHUB_OUTPUT'] + end + + desc "Upload the release AAB to Play" + lane :beta do + begin + upload_to_play_store( + package_name: PACKAGE_NAME, + track: ENV["PLAY_TRACK"] || "internal", + release_status: ENV["PLAY_RELEASE_STATUS"] || "draft", + aab: "../build/app/outputs/bundle/release/app-release.aab", + json_key: ENV["PLAY_JSON_KEY_PATH"], + skip_upload_metadata: true, + skip_upload_changelogs: true, + skip_upload_images: true, + skip_upload_screenshots: true, + ) + rescue => e + # Bootstrap: the publisher API cannot create apps. Until NullFeed is + # created once in the Play Console UI, uploads are a graceful no-op + # (the signed AAB is still attached as a workflow artifact). + if e.message.to_s =~ /[Pp]ackage not found|applicationNotFound/ + UI.important("codes.julian.nullfeed doesn't exist in the Play Console yet - create it once in the Console UI, then re-run. Skipping upload.") + else + raise + end + end + end + + desc "Push the Play listing (copy + graphics), no binary" + lane :listing do + upload_to_play_store( + package_name: PACKAGE_NAME, + json_key: ENV["PLAY_JSON_KEY_PATH"], + skip_upload_aab: true, + skip_upload_apk: true, + skip_upload_changelogs: true, + skip_upload_metadata: false, + skip_upload_images: false, + skip_upload_screenshots: false, + ) + end +end diff --git a/android/fastlane/metadata/android/en-US/changelogs/default.txt b/android/fastlane/metadata/android/en-US/changelogs/default.txt new file mode 100644 index 0000000..49d01db --- /dev/null +++ b/android/fastlane/metadata/android/en-US/changelogs/default.txt @@ -0,0 +1 @@ +First internal build of NullFeed for Android. \ No newline at end of file diff --git a/android/fastlane/metadata/android/en-US/full_description.txt b/android/fastlane/metadata/android/en-US/full_description.txt new file mode 100644 index 0000000..c80be47 --- /dev/null +++ b/android/fastlane/metadata/android/en-US/full_description.txt @@ -0,0 +1,13 @@ +NullFeed is a personal media center app for your self-hosted video library. Browse your collection with a beautiful, cinematic interface. Enjoy features like resume-aware playback, multi-user profiles, and AI-powered content discovery. + +Features: +• Stream your personal video library from your own server +• Continue watching from where you left off on any device +• Multi-user profiles with independent watch history +• Channel-based organization for your content +• AI-powered recommendations to discover new content +• Dark theme optimized for OLED displays + +NullFeed connects to your self-hosted NullFeed server. Set up your server, point the app to it, and enjoy your personal media library from anywhere. + +No ads. No tracking. No promos. No monthly fee. diff --git a/android/fastlane/metadata/android/en-US/images/featureGraphic.png b/android/fastlane/metadata/android/en-US/images/featureGraphic.png new file mode 100644 index 0000000..e6ef6ee Binary files /dev/null and b/android/fastlane/metadata/android/en-US/images/featureGraphic.png differ diff --git a/android/fastlane/metadata/android/en-US/images/icon.png b/android/fastlane/metadata/android/en-US/images/icon.png new file mode 100644 index 0000000..b7b89da Binary files /dev/null and b/android/fastlane/metadata/android/en-US/images/icon.png differ diff --git a/android/fastlane/metadata/android/en-US/images/phoneScreenshots/01_home.png b/android/fastlane/metadata/android/en-US/images/phoneScreenshots/01_home.png new file mode 100644 index 0000000..3c4f66f Binary files /dev/null and b/android/fastlane/metadata/android/en-US/images/phoneScreenshots/01_home.png differ diff --git a/android/fastlane/metadata/android/en-US/images/phoneScreenshots/02_channel_detail.png b/android/fastlane/metadata/android/en-US/images/phoneScreenshots/02_channel_detail.png new file mode 100644 index 0000000..d12bfe7 Binary files /dev/null and b/android/fastlane/metadata/android/en-US/images/phoneScreenshots/02_channel_detail.png differ diff --git a/android/fastlane/metadata/android/en-US/images/phoneScreenshots/03_library.png b/android/fastlane/metadata/android/en-US/images/phoneScreenshots/03_library.png new file mode 100644 index 0000000..7967164 Binary files /dev/null and b/android/fastlane/metadata/android/en-US/images/phoneScreenshots/03_library.png differ diff --git a/android/fastlane/metadata/android/en-US/images/phoneScreenshots/04_discover.png b/android/fastlane/metadata/android/en-US/images/phoneScreenshots/04_discover.png new file mode 100644 index 0000000..ca29dbc Binary files /dev/null and b/android/fastlane/metadata/android/en-US/images/phoneScreenshots/04_discover.png differ diff --git a/android/fastlane/metadata/android/en-US/images/phoneScreenshots/05_profile_picker.png b/android/fastlane/metadata/android/en-US/images/phoneScreenshots/05_profile_picker.png new file mode 100644 index 0000000..36d0bac Binary files /dev/null and b/android/fastlane/metadata/android/en-US/images/phoneScreenshots/05_profile_picker.png differ diff --git a/android/fastlane/metadata/android/en-US/images/tenInchScreenshots/01_home.png b/android/fastlane/metadata/android/en-US/images/tenInchScreenshots/01_home.png new file mode 100644 index 0000000..0793102 Binary files /dev/null and b/android/fastlane/metadata/android/en-US/images/tenInchScreenshots/01_home.png differ diff --git a/android/fastlane/metadata/android/en-US/images/tenInchScreenshots/02_channel_detail.png b/android/fastlane/metadata/android/en-US/images/tenInchScreenshots/02_channel_detail.png new file mode 100644 index 0000000..17cad8d Binary files /dev/null and b/android/fastlane/metadata/android/en-US/images/tenInchScreenshots/02_channel_detail.png differ diff --git a/android/fastlane/metadata/android/en-US/images/tenInchScreenshots/03_library.png b/android/fastlane/metadata/android/en-US/images/tenInchScreenshots/03_library.png new file mode 100644 index 0000000..3473221 Binary files /dev/null and b/android/fastlane/metadata/android/en-US/images/tenInchScreenshots/03_library.png differ diff --git a/android/fastlane/metadata/android/en-US/images/tenInchScreenshots/04_discover.png b/android/fastlane/metadata/android/en-US/images/tenInchScreenshots/04_discover.png new file mode 100644 index 0000000..4a172e1 Binary files /dev/null and b/android/fastlane/metadata/android/en-US/images/tenInchScreenshots/04_discover.png differ diff --git a/android/fastlane/metadata/android/en-US/images/tenInchScreenshots/05_profile_picker.png b/android/fastlane/metadata/android/en-US/images/tenInchScreenshots/05_profile_picker.png new file mode 100644 index 0000000..b58b7cd Binary files /dev/null and b/android/fastlane/metadata/android/en-US/images/tenInchScreenshots/05_profile_picker.png differ diff --git a/android/fastlane/metadata/android/en-US/short_description.txt b/android/fastlane/metadata/android/en-US/short_description.txt new file mode 100644 index 0000000..ac95efa --- /dev/null +++ b/android/fastlane/metadata/android/en-US/short_description.txt @@ -0,0 +1 @@ +Your self-hosted video library — no ads, no tracking, no algorithm. \ No newline at end of file diff --git a/android/fastlane/metadata/android/en-US/title.txt b/android/fastlane/metadata/android/en-US/title.txt new file mode 100644 index 0000000..85dd383 --- /dev/null +++ b/android/fastlane/metadata/android/en-US/title.txt @@ -0,0 +1 @@ +NullFeed \ No newline at end of file diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..fbee1d8 --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,2 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..e4ef43f --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts new file mode 100644 index 0000000..ca7fe06 --- /dev/null +++ b/android/settings.gradle.kts @@ -0,0 +1,26 @@ +pluginManagement { + val flutterSdkPath = + run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.11.1" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false +} + +include(":app")