diff --git a/.forgejo/workflows/build.yaml b/.forgejo/workflows/build.yaml new file mode 100644 index 0000000..26bece1 --- /dev/null +++ b/.forgejo/workflows/build.yaml @@ -0,0 +1,149 @@ +name: Flutter Build (Signed APK) +on: + push: + branches: + - ci-dev + tags: ["*"] + workflow_dispatch: + +env: + FLUTTER_PATH: "flutter" # Путь к подмодулю Flutter + +jobs: + build: + runs-on: docker + container: + image: codeberg.org/mi6e4ka/android-build-ct:latest + env: + TAR_OPTIONS: "--no-same-owner" + # defaults: + # run: + # working-directory: /home/runner/openstore + steps: + # 1. Checkout репозиторий + подмодули + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: recursive + fetch-depth: 0 + + # - name: Pull flutter tags + # run: | + # cd flutter && git fetch --tags --depth=1 && cd .. + + # - name: Set up JDK 21 + # uses: https://github.com/actions/setup-java@v3 + # with: + # java-version: "21" + # distribution: "temurin" + + # - name: Cache Android SDK + # uses: actions/cache@v3 + # with: + # path: | + # ~/android-sdk + # ~/.android + # key: ${{ runner.os }}-android-${{ hashFiles('android/build.gradle.kts') }} + + # - name: Setup Android SDK + # uses: https://github.com/android-actions/setup-android@v3 + + # - name: Cache Flutter + # uses: actions/cache@v3 + # with: + # path: | + # ./flutter/bin/cache + # key: ${{ runner.os }}-flutter-${{ hashFiles('.gitmodules') }} + + - name: Flutter install and check + run: ./flutter/bin/flutter doctor -v + + # 3. Создаем key.properties и загружаем ключ (секреты Forgejo) + #- name: Setup signing keys + # run: | + # # Создаем директорию для ключа + # mkdir -p android/app + # + # # Создаем key.properties + # echo "storePassword=${{ secrets.KEY_PASSWORD }}" > android/key.properties + # echo "keyPassword=${{ secrets.KEY_PASSWORD }}" >> android/key.properties + # echo "keyAlias=upload" >> android/key.properties + # echo "storeFile=key.jks" >> android/key.properties + # + # # Декодируем base64-ключ (хранится в secrets.KEYSTORE_BASE64) + # echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 --decode > android/app/key.jks + + # 4. Кэширование + # - name: Cache dependencies + # uses: actions/cache@v4 + # with: + # path: | + # flutter/bin/cache + # .dart_tool + # key: ${{ runner.os }}-flutter-${{ hashFiles('pubspec.lock') }} + + # 6. Собираем подписанный APK + + # - name: Cache Build + # uses: actions/cache@v3 + # with: + # path: | + # ./android/.gradle + # ~/.gradle + # key: ${{ runner.os }}-android-${{ hashFiles('android/') }} + + - name: Build unsigned APK + run: | + ./flutter/bin/flutter build apk --release --split-per-abi -v + + - name: Sign apk + uses: https://github.com/ilharp/sign-android-release@v2 + id: sign_app + with: + releaseDir: build/app/outputs/flutter-apk + signingKey: ${{ secrets.KEYSTORE_BASE64 }} + keyAlias: upload + keyStorePassword: ${{ secrets.KEY_PASSWORD }} + keyPassword: ${{ secrets.KEY_PASSWORD }} + + - name: Print build dir + run: pwd + + - name: List release files + run: ls build/app/outputs/flutter-apk + + # 7. Сохраняем артефакты + - name: Upload release apk + uses: actions/upload-artifact@v3 + with: + name: app-release + path: build/app/outputs/flutter-apk/*-signed.apk + + - name: Get Release ID + id: get_release + run: | + RESPONSE=$(curl -s -H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \ + $GITHUB_API_URL/repos/$GITHUB_REPOSITORY/releases/tags/$GITHUB_REF_NAME) + RELEASE_ID=$(echo "$RESPONSE" | grep -o '"id":[0-9]*' | head -n1 | grep -o '[0-9]*') + if [ -z "$RELEASE_ID" ]; then + RELEASE_ID=null + fi + echo "release_id=$RELEASE_ID" >> $GITHUB_OUTPUT + + - name: Upload Asset + if: steps.get_release.outputs.release_id != 'null' + run: | + curl -X POST -H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \ + -H "Content-Type: multipart/form-data" \ + -F "attachment=@build/app/outputs/flutter-apk/app-arm64-v8a-release-signed.apk" \ + "${{ env.GITHUB_API_URL }}/repos/${{ env.GITHUB_REPOSITORY }}/releases/${{ steps.get_release.outputs.release_id }}/assets?name=app-arm64-v8a-release.apk" + + curl -X POST -H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \ + -H "Content-Type: multipart/form-data" \ + -F "attachment=@build/app/outputs/flutter-apk/app-armeabi-v7a-release-signed.apk" \ + "${{ env.GITHUB_API_URL }}/repos/${{ env.GITHUB_REPOSITORY }}/releases/${{ steps.get_release.outputs.release_id }}/assets?name=app-armeabi-v7a-release.apk" + + curl -X POST -H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \ + -H "Content-Type: multipart/form-data" \ + -F "attachment=@build/app/outputs/flutter-apk/app-x86_64-release-signed.apk" \ + "${{ env.GITHUB_API_URL }}/repos/${{ env.GITHUB_REPOSITORY }}/releases/${{ steps.get_release.outputs.release_id }}/assets?name=app-x86_64-release.apk" diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..37aa38f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "flutter"] + path = flutter + url = https://github.com/flutter/flutter.git diff --git a/SAFETY.md b/SAFETY.md new file mode 100644 index 0000000..31c4494 --- /dev/null +++ b/SAFETY.md @@ -0,0 +1,5 @@ +# SAFETY + +> Все приложения опубликованные в RuStore проходят обязательную проверку на вирусы и опасные разрешения, в том числе предоставленные из сторонних источников. Однако, вся ответственность за установленные приложения лежит на конечном пользователе. + +[Как RuStore проверяет приложения](https://www.rustore.ru/help/users/authorization/trust-rustore) diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index efa93dc..12dfc5b 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -17,7 +17,8 @@ if (keystorePropertiesFile.exists()) { android { namespace = "dev.mi6e4ka.openstore" compileSdk = flutter.compileSdkVersion - ndkVersion = flutter.ndkVersion + // ndkVersion = flutter.ndkVersion + ndkVersion = "27.2.12479018" compileOptions { sourceCompatibility = JavaVersion.VERSION_11 @@ -40,17 +41,19 @@ android { } signingConfigs { create("release") { - keyAlias = keystoreProperties["keyAlias"] as String - keyPassword = keystoreProperties["keyPassword"] as String - storeFile = keystoreProperties["storeFile"]?.let { file(it) } - storePassword = keystoreProperties["storePassword"] as String + if (keystorePropertiesFile.exists()) { + keyAlias = keystoreProperties["keyAlias"] as String + keyPassword = keystoreProperties["keyPassword"] as String + storeFile = keystoreProperties["storeFile"]?.let { file(it) } + storePassword = keystoreProperties["storePassword"] as String + } } } buildTypes { release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig = signingConfigs.getByName("release") + signingConfig = null + isMinifyEnabled = true + isShrinkResources = true } } dependenciesInfo { diff --git a/flutter b/flutter new file mode 160000 index 0000000..c236373 --- /dev/null +++ b/flutter @@ -0,0 +1 @@ +Subproject commit c23637390482d4cf9598c3ce3f2be31aa7332daf diff --git a/lib/app_page.dart b/lib/app_page.dart index ff0c210..6c5cba7 100644 --- a/lib/app_page.dart +++ b/lib/app_page.dart @@ -11,6 +11,47 @@ class AppPage extends StatefulWidget { State createState() => _AppPageState(); } +String getAndroidVersion(int apiLevel) { + const versions = { + 1: '1.0', + 2: '1.1', + 3: '1.5', + 4: '1.6', + 5: '2.0', + 6: '2.0.1', + 7: '2.1', + 8: '2.2', + 9: '2.3', + 10: '2.3.3', + 11: '3.0', + 12: '3.1', + 13: '3.2', + 14: '4.0', + 15: '4.0.3', + 16: '4.1', + 17: '4.2', + 18: '4.3', + 19: '4.4', + 20: '4.4W', // Wear-версия + 21: '5.0', + 22: '5.1', + 23: '6.0', + 24: '7.0', + 25: '7.1', + 26: '8.0', + 27: '8.1', + 28: '9.0', + 29: '10', + 30: '11', + 31: '12', + 32: '12L', // Android 12L + 33: '13', + 34: '14', + }; + + return versions[apiLevel] ?? '? (API $apiLevel)'; +} + class _AppPageState extends State { Map? _appInfo; @override @@ -56,6 +97,41 @@ class _AppPageState extends State { ? ListView( padding: const EdgeInsets.fromLTRB(15, 0, 15, 15), children: [ + Card( + margin: const EdgeInsets.fromLTRB(0, 0, 0, 15), + surfaceTintColor: _appInfo?["aggregatorInfo"] == null + ? const Color.fromARGB(255, 0, 255, 0) + : const Color.fromARGB(255, 255, 187, 0), + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + spacing: 16, + children: [ + const Icon( + Icons.security, + size: 32, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "предоставлено ${_appInfo?["aggregatorInfo"]?["source"] ?? "RuStore"}", + style: TextStyle(fontSize: 16), + ), + Text( + _appInfo?["aggregatorInfo"] == null + ? (_appInfo?["companyLegalForm"] == + "INDIVIDUAL" + ? "разработчик - частное лицо" + : "разработчик - компания") + : "разработчик: ${_appInfo?["aggregatorInfo"]?["companyName"]}", + style: const TextStyle(fontSize: 14), + ) + ], + ) + ], + ), + )), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -121,8 +197,9 @@ class _AppPageState extends State { bottomText: "Версия", ), _InfoCard( - topText: _appInfo?["minSdkVersion"].toString() ?? "", - bottomText: "minSdkVersion", + topText: + "${getAndroidVersion(_appInfo?["minSdkVersion"] ?? 0)}+", + bottomText: "Android", ), _InfoCard( topText: diff --git a/lib/main.dart b/lib/main.dart index ce12f8a..ad2eada 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -22,7 +22,9 @@ final _router = GoRouter( path: 'instruction', builder: (context, state) { print(state.uri.queryParameters["utm_campaign"]); - return AppPage(packageName: "ru.nspk.mirpay"); + return AppPage( + packageName: state.uri.queryParameters["utm_campaign"] ?? + "ru.nspk.mirpay"); }) ]), GoRoute( @@ -45,10 +47,6 @@ class MyApp extends StatelessWidget { builder: (lightColorScheme, darkColorScheme) => MaterialApp.router( title: 'OpenStore', theme: ThemeData( - pageTransitionsTheme: const PageTransitionsTheme(builders: { - TargetPlatform.android: - PredictiveBackPageTransitionsBuilder(), - }), colorScheme: darkColorScheme ?? ColorScheme.fromSeed( seedColor: Colors.blue, @@ -74,7 +72,31 @@ class HomePage extends StatelessWidget { child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Container(), + Padding( + padding: EdgeInsets.all(10), + child: InkWell( + borderRadius: BorderRadius.circular(12), + onTap: () => launchUrlString( + "https://codeberg.org/mi6e4ka/openstore/src/branch/main/SAFETY.md"), + child: const Card( + child: Padding( + padding: EdgeInsets.all(10), + child: Row( + spacing: 8, + children: [ + Icon( + Icons.info_rounded, + size: 32, + ), + Text( + "О безопасности приложений", + style: TextStyle(fontSize: 18), + ) + ], + ), + ), + )), + ), Row( children: [ const SizedBox( @@ -107,7 +129,7 @@ class HomePage extends StatelessWidget { children: [ InkWell( onTap: () async => await launchUrlString( - "https://git.mi6e4ka.dev/mi6e4ka/openstore"), + "https://codeberg.org/mi6e4ka/openstore"), child: const Text( "source code", style: TextStyle( @@ -136,7 +158,7 @@ class HomePage extends StatelessWidget { : Container(), ], ), - const Text("v1.2.1") + const Text("v1.3.1") ], ), ) diff --git a/pubspec.yaml b/pubspec.yaml index ae1ab9d..de36565 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.2.1+2 +version: 1.3.1+4 environment: sdk: ">=3.4.3 <4.0.0"