├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug-report.yml │ ├── config.yml │ └── feature-request.yml └── workflows │ └── ci.yml ├── .gitignore ├── .idea ├── .gitignore ├── .name ├── compiler.xml ├── deploymentTargetDropDown.xml ├── gradle.xml ├── inspectionProfiles │ └── Project_Default.xml ├── kotlinc.xml ├── migrations.xml ├── misc.xml ├── other.xml └── vcs.xml ├── LICENSE ├── README-ja.md ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro ├── release │ └── output-metadata.json └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── bnyro │ │ └── recorder │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── bnyro │ │ │ └── recorder │ │ │ ├── App.kt │ │ │ ├── canvas_overlay │ │ │ ├── Canvas.kt │ │ │ ├── CanvasOverlay.kt │ │ │ ├── CanvasViewModel.kt │ │ │ └── ToolbarView.kt │ │ │ ├── enums │ │ │ ├── AudioChannels.kt │ │ │ ├── AudioDeviceSource.kt │ │ │ ├── AudioSource.kt │ │ │ ├── RecorderState.kt │ │ │ ├── RecorderType.kt │ │ │ ├── SortOrder.kt │ │ │ ├── ThemeMode.kt │ │ │ ├── TrimmerState.kt │ │ │ └── VideoFormat.kt │ │ │ ├── obj │ │ │ ├── AboutItem.kt │ │ │ ├── AudioFormat.kt │ │ │ ├── RecordingItem.kt │ │ │ └── VideoResolution.kt │ │ │ ├── receivers │ │ │ └── FinishedNotificationReceiver.kt │ │ │ ├── services │ │ │ ├── AudioRecorderService.kt │ │ │ ├── AudioRecorderTile.kt │ │ │ ├── LosslessRecorderService.kt │ │ │ ├── RecorderService.kt │ │ │ ├── ScreenRecorderService.kt │ │ │ └── ScreenRecorderTile.kt │ │ │ ├── ui │ │ │ ├── Destination.kt │ │ │ ├── MainActivity.kt │ │ │ ├── NavHost.kt │ │ │ ├── common │ │ │ │ ├── BlobIconBox.kt │ │ │ │ ├── CheckboxPref.kt │ │ │ │ ├── ChipSelector.kt │ │ │ │ ├── ClickableIcon.kt │ │ │ │ ├── CustomNumInputPref.kt │ │ │ │ ├── DialogButton.kt │ │ │ │ ├── FullscreenDialog.kt │ │ │ │ ├── ResponsiveRecordScreenLayout.kt │ │ │ │ └── SelectionDialog.kt │ │ │ ├── components │ │ │ │ ├── AudioVisualizer.kt │ │ │ │ ├── MiniPlayer.kt │ │ │ │ ├── NamingPatternPref.kt │ │ │ │ ├── PlayerController.kt │ │ │ │ ├── PlayerView.kt │ │ │ │ ├── RecorderController.kt │ │ │ │ ├── RecorderPreview.kt │ │ │ │ ├── RecordingItem.kt │ │ │ │ └── RecordingItemList.kt │ │ │ ├── dialogs │ │ │ │ ├── AboutDialog.kt │ │ │ │ └── ConfirmationDialog.kt │ │ │ ├── models │ │ │ │ ├── PlayerModel.kt │ │ │ │ ├── RecorderModel.kt │ │ │ │ ├── ThemeModel.kt │ │ │ │ └── TrimmerModel.kt │ │ │ ├── screens │ │ │ │ ├── HomeScreen.kt │ │ │ │ ├── PlayerScreen.kt │ │ │ │ ├── RecorderScreen.kt │ │ │ │ ├── SettingsScreen.kt │ │ │ │ └── TrimmerScreen.kt │ │ │ ├── theme │ │ │ │ ├── Color.kt │ │ │ │ ├── Theme.kt │ │ │ │ └── Type.kt │ │ │ └── views │ │ │ │ └── VideoView.kt │ │ │ └── util │ │ │ ├── FileRepository.kt │ │ │ ├── IntentHelper.kt │ │ │ ├── MediaTrimmer.kt │ │ │ ├── NotificationHelper.kt │ │ │ ├── PcmConverter.kt │ │ │ ├── PermissionHelper.kt │ │ │ ├── PickFolderContract.kt │ │ │ ├── PlayerHelper.kt │ │ │ ├── Preferences.kt │ │ │ └── ShortcutHelper.kt │ └── res │ │ ├── drawable-v24 │ │ ├── ic_launcher_foreground.xml │ │ └── ic_launcher_monochrome.xml │ │ ├── drawable │ │ ├── blob.xml │ │ ├── ic_audio.xml │ │ ├── ic_eraser_black_24dp.xml │ │ ├── ic_mic.xml │ │ ├── ic_notification.xml │ │ ├── ic_screen.xml │ │ ├── ic_screen_record.xml │ │ └── ic_trimmer.xml │ │ ├── mipmap-anydpi-v26 │ │ └── ic_launcher.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-ar │ │ └── strings.xml │ │ ├── values-az │ │ └── strings.xml │ │ ├── values-be │ │ └── strings.xml │ │ ├── values-bn │ │ └── strings.xml │ │ ├── values-ca │ │ └── strings.xml │ │ ├── values-cs │ │ └── strings.xml │ │ ├── values-da │ │ └── strings.xml │ │ ├── values-de │ │ └── strings.xml │ │ ├── values-es │ │ └── strings.xml │ │ ├── values-et │ │ └── strings.xml │ │ ├── values-fa │ │ └── strings.xml │ │ ├── values-fi │ │ └── strings.xml │ │ ├── values-fil │ │ └── strings.xml │ │ ├── values-fr │ │ └── strings.xml │ │ ├── values-hi │ │ └── strings.xml │ │ ├── values-ia │ │ └── strings.xml │ │ ├── values-in │ │ └── strings.xml │ │ ├── values-it │ │ └── strings.xml │ │ ├── values-iw │ │ └── strings.xml │ │ ├── values-ja │ │ └── strings.xml │ │ ├── values-ms │ │ └── strings.xml │ │ ├── values-my │ │ └── strings.xml │ │ ├── values-nb-rNO │ │ └── strings.xml │ │ ├── values-night │ │ └── colors.xml │ │ ├── values-nn │ │ └── strings.xml │ │ ├── values-or │ │ └── strings.xml │ │ ├── values-pl │ │ └── strings.xml │ │ ├── values-pt-rBR │ │ └── strings.xml │ │ ├── values-pt │ │ └── strings.xml │ │ ├── values-ro │ │ └── strings.xml │ │ ├── values-ru │ │ └── strings.xml │ │ ├── values-ryu │ │ └── strings.xml │ │ ├── values-sat │ │ └── strings.xml │ │ ├── values-sr │ │ └── strings.xml │ │ ├── values-sv │ │ └── strings.xml │ │ ├── values-ta │ │ └── strings.xml │ │ ├── values-tr │ │ └── strings.xml │ │ ├── values-uk │ │ └── strings.xml │ │ ├── values-zh-rCN │ │ └── strings.xml │ │ ├── values-zh-rTW │ │ └── strings.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── ic_launcher_background.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ ├── backup_rules.xml │ │ ├── data_extraction_rules.xml │ │ └── provider_paths.xml │ └── test │ └── java │ └── com │ └── bnyro │ └── recorder │ └── ExampleUnitTest.kt ├── build.gradle.kts ├── fastlane └── metadata │ └── android │ └── en-US │ ├── changelogs │ └── 16.txt │ ├── full_description.txt │ ├── images │ ├── icon.png │ └── phoneScreenshots │ │ ├── 1-audio-recorder.png │ │ ├── 2-screen-recorder.png │ │ ├── 3-recordings.png │ │ └── 4-settings.png │ └── short_description.txt ├── ghbadge.png ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: Bnyro 2 | liberapay: Bnyro 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | name: 🐞 Bug Report 2 | description: Report an issue in the app 3 | labels: [bug] 4 | body: 5 | 6 | - type: textarea 7 | id: reproduce-steps 8 | attributes: 9 | label: Steps to reproduce 10 | description: Provide an example of the issue. 11 | placeholder: | 12 | Example: 13 | 1. First step 14 | 2. Second step 15 | 3. Issue here 16 | validations: 17 | required: true 18 | 19 | - type: textarea 20 | id: expected-behavior 21 | attributes: 22 | label: Expected behavior 23 | placeholder: | 24 | Example: 25 | "This should happen..." 26 | validations: 27 | required: true 28 | 29 | - type: textarea 30 | id: actual-behavior 31 | attributes: 32 | label: Actual behavior 33 | placeholder: | 34 | Example: 35 | "This happened instead..." 36 | validations: 37 | required: true 38 | 39 | - type: input 40 | id: Record_You-version 41 | attributes: 42 | label: Record You version 43 | description: | 44 | You can find your Record You version in **About**. 45 | placeholder: | 46 | Example: "0.5.0" 47 | validations: 48 | required: true 49 | 50 | - type: input 51 | id: android-version 52 | attributes: 53 | label: Android version 54 | description: | 55 | You can find this somewhere in your Android settings. 56 | placeholder: | 57 | Example: "Android 12" 58 | validations: 59 | required: true 60 | 61 | - type: textarea 62 | id: other-details 63 | attributes: 64 | label: Other details 65 | placeholder: | 66 | Additional details and attachments. 67 | 68 | - type: checkboxes 69 | id: acknowledgements 70 | attributes: 71 | label: Acknowledgements 72 | description: Your issue will be closed if you haven't done these steps. 73 | options: 74 | - label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open issue. 75 | required: true 76 | - label: I have written a short but informative title. 77 | required: true 78 | - label: I will fill out all of the requested information in this form. 79 | required: true 80 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | name: ⭐ Feature request 2 | description: Suggest a feature to improve Record You 3 | labels: [enhancement] 4 | body: 5 | 6 | - type: textarea 7 | id: feature-description 8 | attributes: 9 | label: Describe your suggested feature 10 | description: How can an existing source be improved? 11 | placeholder: | 12 | Example: 13 | "It could work like this..." 14 | validations: 15 | required: true 16 | 17 | - type: textarea 18 | id: other-details 19 | attributes: 20 | label: Other details 21 | placeholder: | 22 | Additional details and attachments. 23 | 24 | - type: checkboxes 25 | id: acknowledgements 26 | attributes: 27 | label: Acknowledgements 28 | description: Your issue will be closed if you haven't done these steps. 29 | options: 30 | - label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open issue. 31 | required: true 32 | - label: I have written a short but informative title. 33 | required: true 34 | - label: I will fill out all of the requested information in this form. 35 | required: true 36 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | paths-ignore: 7 | - "README*.md" 8 | - "fastlane/**" 9 | - "assets/**" 10 | - ".github/**/*.md" 11 | push: 12 | paths-ignore: 13 | - "README*.md" 14 | - "fastlane/**" 15 | - "assets/**" 16 | - ".github/**/*.md" 17 | 18 | jobs: 19 | debug-builds: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v4 23 | - uses: gradle/wrapper-validation-action@v2 24 | 25 | - name: Set up JDK 17 26 | uses: actions/setup-java@v4 27 | with: 28 | java-version: 17 29 | distribution: "temurin" 30 | cache: "gradle" 31 | 32 | - name: Compile 33 | run: | 34 | ./gradlew assembleDebug 35 | 36 | - name: Sign Apk 37 | continue-on-error: true 38 | id: sign_apk 39 | uses: ilharp/sign-android-release@v1 40 | with: 41 | releaseDir: app/build/outputs/apk/debug 42 | signingKey: ${{ secrets.ANDROID_SIGNING_KEY }} 43 | keyAlias: ${{ secrets.ANDROID_KEY_ALIAS }} 44 | keyStorePassword: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} 45 | keyPassword: ${{ secrets.ANDROID_KEY_PASSWORD }} 46 | 47 | - name: Remove file that aren't signed 48 | continue-on-error: true 49 | run: | 50 | ls | grep 'signed\.apk$' && find . -type f -name '*.apk' ! -name '*-signed.apk' -delete 51 | 52 | - name: Upload APK 53 | uses: actions/upload-artifact@v4 54 | with: 55 | name: app 56 | path: app/build/outputs/apk/debug/*.apk 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | Record You -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/deploymentTargetDropDown.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 32 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/migrations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README-ja.md: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |

Record You

5 |

マテリアルデザイン3(You) 音声およびスクリーンレコーダーアプリ

6 | 7 | 8 |
9 | License 10 | Downloads 11 | Last commit 12 | Repo size 13 | Stars 14 |
15 |
16 |
17 | 18 | 他の言語で読む:[英語](https://github.com/you-apps/RecordYou/blob/main/README.md) 19 | 20 | 21 | ## 特徴 22 | 23 | - [x] マテリアルデザイン3(You) 24 | - [x] ダークとライトのテーマ 25 | - [X] 音声および画面録画のサポート 26 | - [X] 利用可能な様々なフォーマットとコーデック 27 | - [X] アプリ内録音プレーヤー 28 | - [X] SAF(ストレージアクセスフレームワーク)のサポート 29 | - [X] Jetpack Composeで書かれています 30 | 31 | 32 | ## Download 33 | 34 |
35 | 36 | [F-Droidで手に入れよう](https://f-droid.org/packages/com.bnyro.recorder/) 37 | [Get it on GitHub](https://github.com/you-apps/RecordYou/releases) 38 | 39 |
40 | 41 | 42 | ## Screenshots 43 |
44 | 45 | 46 | 47 | 48 |
49 | 50 | 51 | ## フィードバックと貢献 52 | ***全ての貢献を歓迎します!*** 53 | 54 | * アプリについてのディスカッションについては、[マトリックス ルーム](https://matrix.to/#/#you-apps:matrix.org)にお気軽にご参加ください。 55 | * バグレポートや機能リクエストは、[こちら](https://github.com/you-apps/RecordYou/issues) から送信できます (要求された情報は全て正しく入力してください)。 56 | * あなたが開発者で、アプリに貢献したい場合は、プロジェクトを**fork**し、[**pull request**](https://help.github.com/articles/about-pull-requests)を送信してください。 57 | 58 | ## 翻訳 59 | 60 | Translation status 61 | 62 | 63 | ## クレジット 64 | * アイコンデザイン:[M00NJ](https://github.com/M00NJ) 65 | 66 | ## License 67 | 68 | Record Youは [**GNU General Public License**](https://www.gnu.org/licenses/gpl.html) に基づいてライセンスが付与されています。必要に応じて使用、学習、共有できます。 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!WARNING] 2 | > This repository is no longer maintained! The app is considered feature-complete though and will continue to work in the future, even though no further updates will be released. 3 | 4 | 5 |
6 | 7 |

Record You

8 |

Privacy focused voice and screen recorder app built with MD3.

9 | 10 | 11 |
12 | License 13 | Downloads 14 | Last commit 15 | Repo size 16 | Stars 17 |
18 |
19 |
20 | 21 | Read in other languages: [Japanese](https://github.com/kuragehimekurara1/RecordYou/blob/main/README-ja.md) 22 | 23 | 24 | ## Features 25 | 26 | - [x] Material Design 3 (You) 27 | - [x] Dark and light theme 28 | - [X] Support for audio and screen recording 29 | - [X] Different available formats and codecs 30 | - [X] In-app recordings player 31 | - [X] SAF (Storage Access Framework) support 32 | - [X] Written in Jetpack Compose 33 | 34 | 35 | ## Download 36 | 37 |
38 | 39 | [Get it on F-Droid](https://f-droid.org/packages/com.bnyro.recorder/) 40 | [Get it on GitHub](https://github.com/you-apps/RecordYou/releases) 41 | 42 |
43 | 44 | 45 | ## Screenshots 46 |
47 | 48 | 49 | 50 | 51 |
52 | 53 | 54 | ## Feedback and contributions 55 | ***All contributions are very welcome!*** 56 | 57 | * Feel free to join the [Matrix room](https://matrix.to/#/#you-apps:matrix.org) for discussions about the app. 58 | * Bug reports and feature requests can be submitted [here](https://github.com/you-apps/RecordYou/issues) (please make sure to fill out all the requested information properly!). 59 | * If you are a developer and wish to contribute to the app, please **fork** the project and submit a [**pull request**](https://help.github.com/articles/about-pull-requests/). 60 | 61 | ## Translation 62 | 63 | Translation status 64 | 65 | 66 | ## Credits 67 | * Icon design by [M00NJ](https://github.com/M00NJ) 68 | 69 | ## License 70 | 71 | Record You is licensed under the [**GNU General Public License**](https://www.gnu.org/licenses/gpl.html): You can use, study and share it as you want. 72 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | id("org.jetbrains.kotlin.android") 4 | } 5 | 6 | android { 7 | namespace = "com.bnyro.recorder" 8 | compileSdk = 34 9 | 10 | defaultConfig { 11 | applicationId = "com.bnyro.recorder" 12 | minSdk = 21 13 | targetSdk = 34 14 | versionCode = 19 15 | versionName = "8.0" 16 | 17 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 18 | vectorDrawables { 19 | useSupportLibrary = true 20 | } 21 | } 22 | 23 | buildTypes { 24 | release { 25 | isMinifyEnabled = true 26 | proguardFiles( 27 | getDefaultProguardFile("proguard-android-optimize.txt"), 28 | "proguard-rules.pro" 29 | ) 30 | } 31 | debug { 32 | isDebuggable = true 33 | applicationIdSuffix = ".debug" 34 | } 35 | } 36 | compileOptions { 37 | sourceCompatibility = JavaVersion.VERSION_1_8 38 | targetCompatibility = JavaVersion.VERSION_1_8 39 | } 40 | kotlinOptions { 41 | jvmTarget = "1.8" 42 | } 43 | buildFeatures { 44 | compose = true 45 | buildConfig = true 46 | } 47 | composeOptions { 48 | kotlinCompilerExtensionVersion = "1.5.9" 49 | } 50 | packagingOptions { 51 | resources { 52 | excludes += "/META-INF/{AL2.0,LGPL2.1}" 53 | } 54 | } 55 | } 56 | 57 | dependencies { 58 | // Core & runtime 59 | implementation("androidx.core:core-ktx:1.12.0") 60 | implementation("androidx.lifecycle:lifecycle-service:2.6.1") 61 | implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0") 62 | implementation("androidx.documentfile:documentfile:1.0.1") 63 | 64 | val media3Version = "1.2.1" 65 | 66 | // For media playback using ExoPlayer 67 | implementation("androidx.media3:media3-exoplayer:$media3Version") 68 | implementation("androidx.media3:media3-ui:$media3Version") 69 | 70 | // Compose 71 | implementation(platform("androidx.compose:compose-bom:2024.02.00")) 72 | implementation("androidx.compose.ui:ui") 73 | implementation("androidx.compose.ui:ui-graphics") 74 | implementation("androidx.compose.ui:ui-tooling-preview") 75 | implementation("androidx.compose.material3:material3") 76 | implementation("androidx.compose.animation:animation-graphics") 77 | 78 | implementation("androidx.activity:activity-compose:1.8.2") 79 | implementation("androidx.compose.material:material-icons-extended") 80 | 81 | //Navigation 82 | implementation("androidx.navigation:navigation-compose:2.7.7") 83 | 84 | // Testing 85 | testImplementation("junit:junit:4.13.2") 86 | androidTestImplementation("androidx.test.ext:junit:1.1.5") 87 | androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") 88 | androidTestImplementation(platform("androidx.compose:compose-bom:2024.02.00")) 89 | androidTestImplementation("androidx.compose.ui:ui-test-junit4") 90 | debugImplementation("androidx.compose.ui:ui-tooling") 91 | debugImplementation("androidx.compose.ui:ui-test-manifest") 92 | } 93 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/release/output-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "artifactType": { 4 | "type": "APK", 5 | "kind": "Directory" 6 | }, 7 | "applicationId": "com.bnyro.recorder", 8 | "variantName": "release", 9 | "elements": [ 10 | { 11 | "type": "SINGLE", 12 | "filters": [], 13 | "attributes": [], 14 | "versionCode": 13, 15 | "versionName": "5.3", 16 | "outputFile": "app-release.apk" 17 | } 18 | ], 19 | "elementType": "File" 20 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/bnyro/recorder/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import org.junit.Assert.* 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | /** 10 | * Instrumented test, which will execute on an Android device. 11 | * 12 | * See [testing documentation](http://d.android.com/tools/testing). 13 | */ 14 | @RunWith(AndroidJUnit4::class) 15 | class ExampleInstrumentedTest { 16 | @Test 17 | fun useAppContext() { 18 | // Context of the app under test. 19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 20 | assertEquals("com.bnyro.recorder", appContext.packageName) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 24 | 25 | 35 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 52 | 53 | 57 | 58 | 62 | 63 | 67 | 68 | 73 | 76 | 77 | 78 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/App.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder 2 | 3 | import android.app.Application 4 | import com.bnyro.recorder.util.FileRepository 5 | import com.bnyro.recorder.util.FileRepositoryImpl 6 | import com.bnyro.recorder.util.NotificationHelper 7 | import com.bnyro.recorder.util.Preferences 8 | import com.bnyro.recorder.util.ShortcutHelper 9 | 10 | class App : Application() { 11 | val fileRepository: FileRepository by lazy { 12 | FileRepositoryImpl(this) 13 | } 14 | override fun onCreate() { 15 | super.onCreate() 16 | Preferences.init(this) 17 | NotificationHelper.buildNotificationChannels(this) 18 | ShortcutHelper.createShortcuts(this) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/canvas_overlay/CanvasViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.canvas_overlay 2 | 3 | import androidx.compose.runtime.getValue 4 | import androidx.compose.runtime.mutableStateListOf 5 | import androidx.compose.runtime.mutableStateOf 6 | import androidx.compose.runtime.setValue 7 | import androidx.lifecycle.ViewModel 8 | 9 | class CanvasViewModel : ViewModel() { 10 | var currentPath by mutableStateOf(PathProperties()) 11 | val paths = mutableStateListOf() 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/canvas_overlay/ToolbarView.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.canvas_overlay 2 | 3 | import android.os.Build 4 | import androidx.compose.foundation.layout.Row 5 | import androidx.compose.material.icons.Icons 6 | import androidx.compose.material.icons.rounded.Clear 7 | import androidx.compose.material.icons.rounded.Draw 8 | import androidx.compose.material.icons.rounded.Pause 9 | import androidx.compose.material.icons.rounded.PlayArrow 10 | import androidx.compose.material.icons.rounded.Stop 11 | import androidx.compose.material3.Card 12 | import androidx.compose.material3.Icon 13 | import androidx.compose.material3.IconButton 14 | import androidx.compose.runtime.Composable 15 | import androidx.compose.runtime.getValue 16 | import androidx.compose.runtime.mutableStateOf 17 | import androidx.compose.runtime.remember 18 | import androidx.compose.runtime.setValue 19 | import androidx.compose.ui.Modifier 20 | import androidx.compose.ui.res.painterResource 21 | import androidx.compose.ui.res.stringResource 22 | import androidx.lifecycle.viewmodel.compose.viewModel 23 | import com.bnyro.recorder.R 24 | import com.bnyro.recorder.enums.RecorderState 25 | import com.bnyro.recorder.ui.models.RecorderModel 26 | 27 | @Composable 28 | fun ToolbarView( 29 | modifier: Modifier = Modifier, 30 | hideCanvas: (Boolean) -> Unit, 31 | canvasViewModel: CanvasViewModel = viewModel(), 32 | recorderModel: RecorderModel = viewModel() 33 | ) { 34 | var currentDrawMode by remember { mutableStateOf(DrawMode.Eraser) } 35 | Card(modifier) { 36 | Row { 37 | IconButton( 38 | onClick = { 39 | currentDrawMode = if (currentDrawMode == DrawMode.Eraser) { 40 | hideCanvas(false) 41 | DrawMode.Pen 42 | } else { 43 | DrawMode.Eraser 44 | } 45 | canvasViewModel.currentPath.drawMode = currentDrawMode 46 | } 47 | ) { 48 | if (currentDrawMode == DrawMode.Eraser) { 49 | Icon( 50 | imageVector = Icons.Rounded.Draw, 51 | contentDescription = stringResource(R.string.draw_mode) 52 | ) 53 | } else { 54 | Icon( 55 | painter = painterResource(id = R.drawable.ic_eraser_black_24dp), 56 | contentDescription = stringResource(R.string.erase_mode) 57 | ) 58 | } 59 | } 60 | IconButton(onClick = { 61 | hideCanvas(true) 62 | canvasViewModel.paths.clear() 63 | }) { 64 | Icon(Icons.Rounded.Clear, "Show/Hide Canvas") 65 | } 66 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 67 | IconButton(onClick = { 68 | if (recorderModel.recorderState == RecorderState.PAUSED) { 69 | recorderModel.resumeRecording() 70 | } else { 71 | recorderModel.pauseRecording() 72 | } 73 | }) { 74 | if (recorderModel.recorderState == RecorderState.PAUSED) { 75 | Icon( 76 | Icons.Rounded.PlayArrow, 77 | contentDescription = stringResource(id = R.string.resume) 78 | ) 79 | } else { 80 | Icon( 81 | Icons.Rounded.Pause, 82 | contentDescription = stringResource(id = R.string.pause) 83 | ) 84 | } 85 | } 86 | } 87 | IconButton(onClick = { 88 | recorderModel.stopRecording() 89 | }) { 90 | Icon(Icons.Rounded.Stop, stringResource(id = R.string.stop)) 91 | } 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/enums/AudioChannels.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.enums 2 | 3 | enum class AudioChannels(val value: Int) { 4 | MONO(1), 5 | STEREO(2); 6 | 7 | companion object { 8 | fun fromInt(value: Int) = AudioChannels.values().first { it.value == value } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/enums/AudioDeviceSource.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.enums 2 | 3 | import android.media.MediaRecorder 4 | 5 | enum class AudioDeviceSource(val value: Int) { 6 | DEFAULT(MediaRecorder.AudioSource.DEFAULT), 7 | MIC(MediaRecorder.AudioSource.MIC), 8 | CAMCORDER(MediaRecorder.AudioSource.CAMCORDER), 9 | UNPROCESSED(MediaRecorder.AudioSource.UNPROCESSED), 10 | REMOTE_SUBMIX(MediaRecorder.AudioSource.REMOTE_SUBMIX); 11 | 12 | companion object { 13 | fun fromInt(value: Int) = AudioDeviceSource.values().first { it.value == value } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/enums/AudioSource.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.enums 2 | 3 | enum class AudioSource(val value: Int) { 4 | NONE(0), 5 | MICROPHONE(1); 6 | 7 | companion object { 8 | fun fromInt(value: Int) = AudioSource.values().first { it.value == value } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/enums/RecorderState.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.enums 2 | 3 | enum class RecorderState { 4 | IDLE, 5 | ACTIVE, 6 | PAUSED 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/enums/RecorderType.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.enums 2 | 3 | enum class RecorderType { 4 | NONE, 5 | AUDIO, 6 | VIDEO 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/enums/SortOrder.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.enums 2 | 3 | enum class SortOrder { 4 | MODIFIED, 5 | MODIFIED_REV, 6 | ALPHABETIC, 7 | ALPHABETIC_REV, 8 | SIZE, 9 | SIZE_REV 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/enums/ThemeMode.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.enums 2 | 3 | import com.bnyro.recorder.util.Preferences 4 | 5 | enum class ThemeMode { 6 | SYSTEM, 7 | LIGHT, 8 | DARK, 9 | AMOLED 10 | ; 11 | 12 | companion object { 13 | fun getCurrent() = valueOf(Preferences.getString(Preferences.themeModeKey, SYSTEM.name)) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/enums/TrimmerState.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.enums 2 | 3 | sealed interface TrimmerState { 4 | object NoJob : TrimmerState 5 | object Running : TrimmerState 6 | object Success : TrimmerState 7 | object Failed : TrimmerState 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/enums/VideoFormat.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.enums 2 | 3 | import android.media.MediaRecorder 4 | import android.os.Build 5 | import com.bnyro.recorder.util.Preferences 6 | 7 | data class VideoFormat( 8 | val name: String, 9 | val codec: Int, 10 | val extension: String, 11 | val format: Int 12 | ) { 13 | companion object { 14 | val codecs = mutableListOf( 15 | VideoFormat( 16 | "H.264", 17 | MediaRecorder.VideoEncoder.H264, 18 | "mp4", 19 | MediaRecorder.OutputFormat.MPEG_4 20 | ), 21 | VideoFormat( 22 | "webm (VP8)", 23 | MediaRecorder.VideoEncoder.VP8, 24 | "webm", 25 | MediaRecorder.OutputFormat.WEBM 26 | ) 27 | ).also { 28 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 29 | it.add( 30 | VideoFormat( 31 | "H.265", 32 | MediaRecorder.VideoEncoder.HEVC, 33 | "mp4", 34 | MediaRecorder.OutputFormat.MPEG_4 35 | ) 36 | ) 37 | } 38 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 39 | it.add( 40 | VideoFormat( 41 | "webm (VP9)", 42 | MediaRecorder.VideoEncoder.VP9, 43 | "webm", 44 | MediaRecorder.OutputFormat.WEBM 45 | ) 46 | ) 47 | } 48 | } 49 | 50 | fun getCurrent() = codecs.first { 51 | it.codec == Preferences.prefs.getInt( 52 | Preferences.videoCodecKey, 53 | MediaRecorder.VideoEncoder.H264 54 | ) 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/obj/AboutItem.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.obj 2 | 3 | import androidx.annotation.StringRes 4 | import androidx.compose.ui.graphics.vector.ImageVector 5 | 6 | data class AboutItem( 7 | @StringRes val title: Int, 8 | val icon: ImageVector, 9 | val url: String? = null 10 | ) 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/obj/AudioFormat.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.obj 2 | 3 | import android.media.MediaRecorder 4 | import android.os.Build 5 | import androidx.annotation.RequiresApi 6 | import com.bnyro.recorder.util.Preferences 7 | 8 | data class AudioFormat( 9 | val format: Int, 10 | val codec: Int, 11 | val name: String, 12 | val extension: String 13 | ) { 14 | companion object { 15 | private val m4a = AudioFormat( 16 | MediaRecorder.OutputFormat.MPEG_4, 17 | MediaRecorder.AudioEncoder.HE_AAC, 18 | "M4A", 19 | "m4a" 20 | ) 21 | private val aac = AudioFormat( 22 | MediaRecorder.OutputFormat.AAC_ADTS, 23 | MediaRecorder.AudioEncoder.AAC, 24 | "AAC", 25 | "aac" 26 | ) 27 | private val tgp = AudioFormat( 28 | MediaRecorder.OutputFormat.THREE_GPP, 29 | MediaRecorder.AudioEncoder.AAC, 30 | "3GP", 31 | "3gp" 32 | ) 33 | 34 | @RequiresApi(Build.VERSION_CODES.Q) 35 | private val opus = AudioFormat( 36 | MediaRecorder.OutputFormat.OGG, 37 | MediaRecorder.AudioEncoder.OPUS, 38 | "OPUS", 39 | "ogg" 40 | ) 41 | 42 | val formats = mutableListOf(m4a, aac, tgp).also { 43 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) it.add(opus) 44 | } 45 | 46 | fun getCurrent() = formats.firstOrNull { 47 | it.name == Preferences.prefs.getString(Preferences.audioFormatKey, m4a.name) 48 | } ?: m4a 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/obj/RecordingItem.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.obj 2 | 3 | import android.graphics.Bitmap 4 | import androidx.documentfile.provider.DocumentFile 5 | import com.bnyro.recorder.enums.RecorderType 6 | 7 | data class RecordingItemData( 8 | val recordingFile: DocumentFile, 9 | val recorderType: RecorderType, 10 | val thumbnail: Bitmap? = null 11 | ) { 12 | val isAudio get() = recorderType == RecorderType.AUDIO 13 | val isVideo get() = recorderType == RecorderType.VIDEO 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/obj/VideoResolution.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.obj 2 | 3 | data class VideoResolution( 4 | val width: Int, 5 | val height: Int, 6 | val density: Int, 7 | val frameRate: Int 8 | ) 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/receivers/FinishedNotificationReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.receivers 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import androidx.core.app.NotificationManagerCompat 7 | import com.bnyro.recorder.App 8 | import com.bnyro.recorder.services.RecorderService 9 | import com.bnyro.recorder.util.IntentHelper 10 | import com.bnyro.recorder.util.NotificationHelper 11 | 12 | class FinishedNotificationReceiver : BroadcastReceiver() { 13 | override fun onReceive(context: Context, intent: Intent) { 14 | val fileName = intent.getStringExtra(RecorderService.FILE_NAME_EXTRA_KEY) ?: return 15 | val file = (context.applicationContext as App).fileRepository 16 | .getOutputDir().findFile(fileName) 17 | 18 | when (intent.getStringExtra(RecorderService.ACTION_EXTRA_KEY)) { 19 | RecorderService.SHARE_ACTION -> file?.let { IntentHelper.shareFile(context, it) } 20 | RecorderService.DELETE_ACTION -> file?.delete() 21 | } 22 | NotificationManagerCompat.from(context) 23 | .cancel(NotificationHelper.RECORDING_FINISHED_N_ID) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/services/AudioRecorderService.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.services 2 | 3 | import android.content.pm.ServiceInfo 4 | import android.media.MediaRecorder 5 | import android.os.Build 6 | import android.widget.Toast 7 | import com.bnyro.recorder.App 8 | import com.bnyro.recorder.R 9 | import com.bnyro.recorder.enums.AudioChannels 10 | import com.bnyro.recorder.enums.AudioDeviceSource 11 | import com.bnyro.recorder.obj.AudioFormat 12 | import com.bnyro.recorder.util.PlayerHelper 13 | import com.bnyro.recorder.util.Preferences 14 | 15 | class AudioRecorderService : RecorderService() { 16 | override val notificationTitle: String 17 | get() = getString(R.string.recording_audio) 18 | 19 | override val fgServiceType: Int? 20 | get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { 21 | ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE 22 | } else { 23 | null 24 | } 25 | 26 | override fun start() { 27 | val audioFormat = AudioFormat.getCurrent() 28 | 29 | recorder = PlayerHelper.newRecorder(this).apply { 30 | val audioSource = Preferences.prefs.getInt( 31 | Preferences.audioDeviceSourceKey, 32 | AudioDeviceSource.DEFAULT.value 33 | ) 34 | setAudioSource(audioSource) 35 | 36 | val sampleRatePref = Preferences.prefs.getInt(Preferences.audioSampleRateKey, -1).takeIf { it > 0 } 37 | val audioBitrate = Preferences.prefs.getInt(Preferences.audioBitrateKey, -1).takeIf { it > 0 } 38 | if (sampleRatePref != null && (audioFormat.codec != MediaRecorder.AudioEncoder.OPUS || sampleRatePref in opusSampleRates)) { 39 | setAudioSamplingRate(sampleRatePref) 40 | } 41 | if (audioBitrate != null) { 42 | setAudioEncodingBitRate(audioBitrate) 43 | } else if (sampleRatePref != null) { 44 | setAudioEncodingBitRate(sampleRatePref * 32 * 2) 45 | } 46 | 47 | Preferences.prefs.getInt(Preferences.audioChannelsKey, AudioChannels.MONO.value).let { 48 | setAudioChannels(it) 49 | } 50 | 51 | setOutputFormat(audioFormat.format) 52 | setAudioEncoder(audioFormat.codec) 53 | 54 | outputFile = (application as App).fileRepository.getOutputFile( 55 | audioFormat.extension 56 | ) 57 | if (outputFile == null) { 58 | Toast.makeText( 59 | this@AudioRecorderService, 60 | R.string.cant_access_selected_folder, 61 | Toast.LENGTH_LONG 62 | ).show() 63 | onDestroy() 64 | return 65 | } 66 | 67 | fileDescriptor = contentResolver.openFileDescriptor(outputFile!!.uri, "w") 68 | setOutputFile(fileDescriptor?.fileDescriptor) 69 | 70 | runCatching { 71 | prepare() 72 | } 73 | 74 | start() 75 | } 76 | 77 | super.start() 78 | } 79 | 80 | override fun getCurrentAmplitude() = recorder?.maxAmplitude 81 | 82 | companion object { 83 | private val opusSampleRates = listOf(8000, 12000, 16000, 24000, 48000) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/services/AudioRecorderTile.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.services 2 | 3 | import android.app.PendingIntent 4 | import android.content.Intent 5 | import android.content.Intent.FLAG_ACTIVITY_NEW_TASK 6 | import android.os.Build 7 | import android.service.quicksettings.TileService 8 | import androidx.annotation.RequiresApi 9 | import com.bnyro.recorder.enums.RecorderType 10 | import com.bnyro.recorder.ui.MainActivity 11 | 12 | @RequiresApi(Build.VERSION_CODES.N) 13 | class AudioRecorderTile : TileService() { 14 | override fun onClick() { 15 | super.onClick() 16 | val intent = Intent(this, MainActivity::class.java) 17 | .putExtra(MainActivity.EXTRA_ACTION_KEY, RecorderType.AUDIO.name) 18 | .apply { 19 | flags = FLAG_ACTIVITY_NEW_TASK 20 | } 21 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { 22 | startActivityAndCollapse( 23 | PendingIntent.getActivity( 24 | this, PENDING_INTENT_REQUEST_CODE, intent, PendingIntent.FLAG_IMMUTABLE 25 | ) 26 | ) 27 | } else { 28 | startActivityAndCollapse(intent) 29 | } 30 | } 31 | 32 | companion object { 33 | const val PENDING_INTENT_REQUEST_CODE = 20 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/services/ScreenRecorderTile.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.services 2 | 3 | import android.app.PendingIntent 4 | import android.content.Intent 5 | import android.os.Build 6 | import android.service.quicksettings.TileService 7 | import androidx.annotation.RequiresApi 8 | import com.bnyro.recorder.enums.RecorderType 9 | import com.bnyro.recorder.ui.MainActivity 10 | 11 | @RequiresApi(Build.VERSION_CODES.N) 12 | class ScreenRecorderTile : TileService() { 13 | override fun onClick() { 14 | super.onClick() 15 | val intent = Intent(this, MainActivity::class.java) 16 | .putExtra(MainActivity.EXTRA_ACTION_KEY, RecorderType.VIDEO.name) 17 | .apply { 18 | flags = Intent.FLAG_ACTIVITY_NEW_TASK 19 | } 20 | 21 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { 22 | startActivityAndCollapse( 23 | PendingIntent.getActivity( 24 | this, PENDING_INTENT_REQUEST_CODE, intent, PendingIntent.FLAG_IMMUTABLE 25 | ) 26 | ) 27 | } else { 28 | startActivityAndCollapse(intent) 29 | } 30 | } 31 | 32 | companion object { 33 | const val PENDING_INTENT_REQUEST_CODE = 21 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/ui/Destination.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.ui 2 | 3 | sealed class Destination(val route: String) { 4 | object Home : Destination("home") 5 | object Settings : Destination("settings") 6 | object RecordingPlayer : Destination("player") 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/ui/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.ui 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.media.projection.MediaProjectionManager 7 | import android.os.Bundle 8 | import androidx.activity.ComponentActivity 9 | import androidx.activity.compose.setContent 10 | import androidx.activity.enableEdgeToEdge 11 | import androidx.activity.result.ActivityResultLauncher 12 | import androidx.activity.result.contract.ActivityResultContracts 13 | import androidx.activity.viewModels 14 | import androidx.compose.foundation.isSystemInDarkTheme 15 | import androidx.compose.foundation.layout.fillMaxSize 16 | import androidx.compose.material3.MaterialTheme 17 | import androidx.compose.material3.Surface 18 | import androidx.compose.ui.Modifier 19 | import androidx.navigation.compose.rememberNavController 20 | import com.bnyro.recorder.enums.RecorderType 21 | import com.bnyro.recorder.enums.ThemeMode 22 | import com.bnyro.recorder.ui.models.RecorderModel 23 | import com.bnyro.recorder.ui.models.ThemeModel 24 | import com.bnyro.recorder.ui.theme.RecordYouTheme 25 | 26 | class MainActivity : ComponentActivity() { 27 | private var initialRecorder = RecorderType.NONE 28 | private var exitAfterRecordingStart = false 29 | private lateinit var mProjectionManager: MediaProjectionManager 30 | private val recorderModel: RecorderModel by viewModels() 31 | private lateinit var launcher: ActivityResultLauncher 32 | 33 | override fun onCreate(savedInstanceState: Bundle?) { 34 | super.onCreate(savedInstanceState) 35 | val themeModel: ThemeModel by viewModels() 36 | launcher = 37 | registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> 38 | if (result.resultCode == Activity.RESULT_OK) { 39 | recorderModel.startVideoRecorder(this, result) 40 | } 41 | } 42 | mProjectionManager = 43 | getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager 44 | 45 | processIntent(intent) 46 | enableEdgeToEdge() 47 | 48 | setContent { 49 | RecordYouTheme( 50 | when (themeModel.themeMode) { 51 | ThemeMode.SYSTEM -> isSystemInDarkTheme() 52 | ThemeMode.DARK, ThemeMode.AMOLED -> true 53 | else -> false 54 | }, 55 | amoledDark = themeModel.themeMode == ThemeMode.AMOLED 56 | ) { 57 | val navController = rememberNavController() 58 | Surface( 59 | modifier = Modifier 60 | .fillMaxSize(), 61 | color = MaterialTheme.colorScheme.background 62 | ) { 63 | AppNavHost( 64 | navController = navController, 65 | modifier = Modifier, 66 | initialRecorder = initialRecorder 67 | ) 68 | } 69 | } 70 | } 71 | } 72 | 73 | override fun onNewIntent(intent: Intent) { 74 | processIntent(intent) 75 | super.onNewIntent(intent) 76 | } 77 | 78 | private fun processIntent(intent: Intent) { 79 | val initialRecorderType = intent.getStringExtra(EXTRA_ACTION_KEY)?.let { 80 | RecorderType.valueOf(it) 81 | } ?: RecorderType.NONE 82 | initialRecorder = initialRecorderType 83 | if (initialRecorderType == RecorderType.AUDIO) { 84 | recorderModel.startAudioRecorder(this) 85 | } else if (initialRecorderType == RecorderType.VIDEO) { 86 | if (recorderModel.hasScreenRecordingPermissions(this)) { 87 | launcher.launch(mProjectionManager.createScreenCaptureIntent()) 88 | } 89 | } 90 | intent.removeExtra(EXTRA_ACTION_KEY) 91 | } 92 | 93 | override fun onPause() { 94 | super.onPause() 95 | if (initialRecorder == RecorderType.VIDEO) { 96 | exitAfterRecordingStart = true 97 | initialRecorder = RecorderType.NONE 98 | } 99 | } 100 | 101 | override fun onResume() { 102 | super.onResume() 103 | if (exitAfterRecordingStart) { 104 | exitAfterRecordingStart = false 105 | moveTaskToBack(true) 106 | } 107 | } 108 | 109 | companion object { 110 | const val EXTRA_ACTION_KEY = "action" 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/ui/NavHost.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.ui 2 | 3 | import androidx.compose.animation.AnimatedContentTransitionScope 4 | import androidx.compose.animation.fadeIn 5 | import androidx.compose.animation.fadeOut 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Modifier 8 | import androidx.navigation.NavHostController 9 | import androidx.navigation.compose.NavHost 10 | import androidx.navigation.compose.composable 11 | import com.bnyro.recorder.enums.RecorderType 12 | import com.bnyro.recorder.ui.screens.HomeScreen 13 | import com.bnyro.recorder.ui.screens.PlayerScreen 14 | import com.bnyro.recorder.ui.screens.SettingsScreen 15 | 16 | @Composable 17 | fun AppNavHost( 18 | navController: NavHostController, 19 | modifier: Modifier = Modifier, 20 | initialRecorder: RecorderType 21 | ) { 22 | NavHost( 23 | navController = navController, 24 | startDestination = Destination.Home.route, 25 | modifier = modifier 26 | ) { 27 | composable(route = Destination.Home.route, 28 | enterTransition = { 29 | slideIntoContainer( 30 | AnimatedContentTransitionScope.SlideDirection.Down, 31 | initialOffset = { it / 4 } 32 | ) + fadeIn() 33 | }, 34 | exitTransition = { 35 | slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Up, 36 | targetOffset = { it / 4 }) + fadeOut() 37 | }) { 38 | HomeScreen(initialRecorder, onNavigate = { destination -> 39 | navController.navigateTo(destination.route) 40 | }) 41 | } 42 | 43 | composable(route = Destination.Settings.route, 44 | enterTransition = { 45 | slideIntoContainer( 46 | AnimatedContentTransitionScope.SlideDirection.Up, 47 | initialOffset = { it / 4 }) + fadeIn() 48 | }, 49 | exitTransition = { 50 | slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Down, 51 | targetOffset = { it / 4 }) + fadeOut() 52 | } 53 | ) { 54 | SettingsScreen() 55 | } 56 | 57 | composable(route = Destination.RecordingPlayer.route, 58 | enterTransition = { 59 | slideIntoContainer( 60 | AnimatedContentTransitionScope.SlideDirection.Up, 61 | initialOffset = { it / 4 }) + fadeIn() 62 | }, 63 | exitTransition = { 64 | slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Down, 65 | targetOffset = { it / 4 }) + fadeOut() 66 | }) { 67 | PlayerScreen(showVideoModeInitially = false) 68 | } 69 | } 70 | } 71 | 72 | fun NavHostController.navigateTo(route: String) = this.navigate(route) { 73 | launchSingleTop = true 74 | restoreState = true 75 | } 76 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/ui/common/BlobIconBox.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.ui.common 2 | 3 | import androidx.annotation.DrawableRes 4 | import androidx.compose.foundation.Image 5 | import androidx.compose.foundation.layout.Box 6 | import androidx.compose.foundation.layout.fillMaxSize 7 | import androidx.compose.foundation.layout.size 8 | import androidx.compose.material3.MaterialTheme 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Alignment 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.draw.alpha 13 | import androidx.compose.ui.graphics.ColorFilter 14 | import androidx.compose.ui.res.painterResource 15 | import androidx.compose.ui.unit.dp 16 | import com.bnyro.recorder.R 17 | 18 | @Composable 19 | fun BlobIconBox(@DrawableRes icon: Int) { 20 | Box( 21 | contentAlignment = Alignment.Center, 22 | modifier = Modifier 23 | .fillMaxSize() 24 | .alpha(0.3f) 25 | ) { 26 | Image( 27 | modifier = Modifier.size(350.dp), 28 | painter = painterResource(id = R.drawable.blob), 29 | contentDescription = null, 30 | colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.secondaryContainer) 31 | ) 32 | Image( 33 | modifier = Modifier.size(250.dp), 34 | painter = painterResource(id = icon), 35 | contentDescription = null, 36 | colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSecondaryContainer) 37 | ) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/ui/common/CheckboxPref.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.ui.common 2 | 3 | import android.view.SoundEffectConstants 4 | import androidx.compose.foundation.clickable 5 | import androidx.compose.foundation.interaction.MutableInteractionSource 6 | import androidx.compose.foundation.layout.Arrangement 7 | import androidx.compose.foundation.layout.Column 8 | import androidx.compose.foundation.layout.Row 9 | import androidx.compose.foundation.layout.Spacer 10 | import androidx.compose.foundation.layout.fillMaxWidth 11 | import androidx.compose.foundation.layout.height 12 | import androidx.compose.foundation.layout.width 13 | import androidx.compose.material3.Checkbox 14 | import androidx.compose.material3.Text 15 | import androidx.compose.runtime.Composable 16 | import androidx.compose.runtime.getValue 17 | import androidx.compose.runtime.mutableStateOf 18 | import androidx.compose.runtime.remember 19 | import androidx.compose.runtime.setValue 20 | import androidx.compose.ui.Alignment 21 | import androidx.compose.ui.Modifier 22 | import androidx.compose.ui.platform.LocalView 23 | import androidx.compose.ui.unit.dp 24 | import androidx.compose.ui.unit.sp 25 | import com.bnyro.recorder.util.Preferences 26 | 27 | @Composable 28 | fun CheckboxPref( 29 | prefKey: String, 30 | title: String, 31 | summary: String? = null, 32 | defaultValue: Boolean = false, 33 | onCheckedChange: (Boolean) -> Unit = {} 34 | ) { 35 | var checked by remember { 36 | mutableStateOf( 37 | Preferences.prefs.getBoolean(prefKey, defaultValue) 38 | ) 39 | } 40 | val interactionSource = remember { MutableInteractionSource() } 41 | val view = LocalView.current 42 | 43 | Row( 44 | modifier = Modifier 45 | .fillMaxWidth() 46 | .clickable( 47 | interactionSource = interactionSource, 48 | indication = null 49 | ) { 50 | view.playSoundEffect(SoundEffectConstants.CLICK) 51 | checked = !checked 52 | Preferences.edit { putBoolean(prefKey, checked) } 53 | }, 54 | horizontalArrangement = Arrangement.SpaceBetween, 55 | verticalAlignment = Alignment.CenterVertically 56 | ) { 57 | Column( 58 | modifier = Modifier.weight(1f), 59 | verticalArrangement = Arrangement.Center 60 | ) { 61 | Text(title) 62 | if (summary != null) { 63 | Spacer(Modifier.height(2.dp)) 64 | Text( 65 | text = summary, 66 | fontSize = 12.sp, 67 | lineHeight = 15.sp 68 | ) 69 | } 70 | } 71 | Spacer(modifier = Modifier.width(5.dp)) 72 | Checkbox( 73 | checked = checked, 74 | onCheckedChange = { 75 | checked = it 76 | Preferences.edit { putBoolean(prefKey, it) } 77 | onCheckedChange.invoke(it) 78 | } 79 | ) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/ui/common/ChipSelector.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.ui.common 2 | 3 | import android.view.SoundEffectConstants 4 | import androidx.compose.foundation.layout.Spacer 5 | import androidx.compose.foundation.layout.height 6 | import androidx.compose.foundation.layout.padding 7 | import androidx.compose.foundation.lazy.LazyRow 8 | import androidx.compose.foundation.lazy.itemsIndexed 9 | import androidx.compose.material.icons.Icons 10 | import androidx.compose.material.icons.filled.Check 11 | import androidx.compose.material3.ElevatedFilterChip 12 | import androidx.compose.material3.ExperimentalMaterial3Api 13 | import androidx.compose.material3.Icon 14 | import androidx.compose.material3.Text 15 | import androidx.compose.runtime.Composable 16 | import androidx.compose.ui.Alignment 17 | import androidx.compose.ui.Modifier 18 | import androidx.compose.ui.draw.scale 19 | import androidx.compose.ui.platform.LocalView 20 | import androidx.compose.ui.semantics.clearAndSetSemantics 21 | import androidx.compose.ui.semantics.contentDescription 22 | import androidx.compose.ui.semantics.heading 23 | import androidx.compose.ui.semantics.selected 24 | import androidx.compose.ui.semantics.semantics 25 | import androidx.compose.ui.text.font.FontWeight 26 | import androidx.compose.ui.unit.dp 27 | import androidx.compose.ui.unit.sp 28 | 29 | @OptIn(ExperimentalMaterial3Api::class) 30 | @Composable 31 | fun ChipSelector( 32 | title: String? = null, 33 | entries: List, 34 | values: List, 35 | selections: List, 36 | onSelectionChanged: (Int, Boolean) -> Unit 37 | ) { 38 | val view = LocalView.current 39 | title?.let { 40 | Text( 41 | text = it, 42 | fontWeight = FontWeight.Bold, 43 | fontSize = 18.sp, 44 | modifier = Modifier.semantics { heading() } 45 | ) 46 | } 47 | Spacer(modifier = Modifier.height(5.dp)) 48 | LazyRow( 49 | verticalAlignment = Alignment.CenterVertically 50 | ) { 51 | itemsIndexed(entries) { index, entry -> 52 | ElevatedFilterChip( 53 | modifier = Modifier 54 | .padding(end = 10.dp) 55 | .clearAndSetSemantics { // We need to suppress calculated contentDescription that contains word selected or not selected based off of the selected state 56 | contentDescription = entry 57 | selected = selections.contains(values[index]) 58 | }, 59 | label = { 60 | Text(entry) 61 | }, 62 | selected = selections.contains(values[index]), 63 | onClick = { 64 | view.playSoundEffect(SoundEffectConstants.CLICK) 65 | onSelectionChanged(index, !selections.contains(values[index])) 66 | }, 67 | leadingIcon = { 68 | if (selections.contains(values[index])) { 69 | Icon( 70 | Icons.Default.Check, 71 | contentDescription = null, 72 | modifier = Modifier.scale(0.6f) 73 | ) 74 | } 75 | } 76 | ) 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/ui/common/ClickableIcon.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.ui.common 2 | 3 | import android.view.SoundEffectConstants 4 | import androidx.compose.material3.Icon 5 | import androidx.compose.material3.IconButton 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Modifier 8 | import androidx.compose.ui.graphics.vector.ImageVector 9 | import androidx.compose.ui.platform.LocalView 10 | 11 | @Composable 12 | fun ClickableIcon( 13 | modifier: Modifier = Modifier, 14 | imageVector: ImageVector, 15 | contentDescription: String? = null, 16 | onClick: () -> Unit 17 | ) { 18 | val view = LocalView.current 19 | IconButton( 20 | modifier = modifier, 21 | onClick = { 22 | view.playSoundEffect(SoundEffectConstants.CLICK) 23 | onClick.invoke() 24 | } 25 | ) { 26 | Icon(imageVector, contentDescription) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/ui/common/CustomNumInputPref.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.ui.common 2 | 3 | import android.view.SoundEffectConstants 4 | import androidx.compose.foundation.layout.Column 5 | import androidx.compose.foundation.layout.Row 6 | import androidx.compose.foundation.layout.Spacer 7 | import androidx.compose.foundation.layout.height 8 | import androidx.compose.foundation.layout.padding 9 | import androidx.compose.foundation.layout.width 10 | import androidx.compose.foundation.text.KeyboardOptions 11 | import androidx.compose.material3.AlertDialog 12 | import androidx.compose.material3.Button 13 | import androidx.compose.material3.Checkbox 14 | import androidx.compose.material3.ExperimentalMaterial3Api 15 | import androidx.compose.material3.OutlinedTextField 16 | import androidx.compose.material3.Text 17 | import androidx.compose.runtime.Composable 18 | import androidx.compose.runtime.getValue 19 | import androidx.compose.runtime.mutableStateOf 20 | import androidx.compose.runtime.remember 21 | import androidx.compose.runtime.setValue 22 | import androidx.compose.ui.Alignment 23 | import androidx.compose.ui.Modifier 24 | import androidx.compose.ui.platform.LocalView 25 | import androidx.compose.ui.res.stringResource 26 | import androidx.compose.ui.text.input.KeyboardType 27 | import androidx.compose.ui.unit.dp 28 | import com.bnyro.recorder.R 29 | import com.bnyro.recorder.util.Preferences 30 | 31 | @OptIn(ExperimentalMaterial3Api::class) 32 | @Composable 33 | fun CustomNumInputPref( 34 | key: String, 35 | title: String, 36 | defValue: Int 37 | ) { 38 | val pref = Preferences.prefs.getInt(key, -1).takeIf { it != -1 } 39 | val view = LocalView.current 40 | var showDialog by remember { 41 | mutableStateOf(false) 42 | } 43 | var isAutoEnabled by remember { 44 | mutableStateOf(pref == null) 45 | } 46 | var input by remember { 47 | mutableStateOf((pref ?: defValue).toString()) 48 | } 49 | 50 | Button(onClick = { 51 | view.playSoundEffect(SoundEffectConstants.CLICK) 52 | showDialog = true 53 | }) { 54 | Text(text = title) 55 | } 56 | 57 | if (showDialog) { 58 | AlertDialog( 59 | onDismissRequest = { showDialog = false }, 60 | title = { Text(text = title) }, 61 | confirmButton = { 62 | DialogButton(stringResource(R.string.okay)) { 63 | Preferences.edit { 64 | putInt(key, input.takeIf { !isAutoEnabled }?.toIntOrNull() ?: -1) 65 | } 66 | showDialog = false 67 | } 68 | }, 69 | dismissButton = { 70 | DialogButton(stringResource(R.string.cancel)) { 71 | showDialog = false 72 | } 73 | }, 74 | text = { 75 | Column { 76 | Row( 77 | verticalAlignment = Alignment.CenterVertically 78 | ) { 79 | Checkbox(checked = isAutoEnabled, onCheckedChange = { isAutoEnabled = it }) 80 | Spacer(modifier = Modifier.width(10.dp)) 81 | Text(text = stringResource(R.string.auto)) 82 | } 83 | Spacer(modifier = Modifier.height(5.dp)) 84 | OutlinedTextField( 85 | modifier = Modifier.padding(horizontal = 5.dp), 86 | value = input, 87 | onValueChange = { input = it }, 88 | enabled = !isAutoEnabled, 89 | readOnly = isAutoEnabled, 90 | keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), 91 | label = { Text(title) } 92 | ) 93 | } 94 | } 95 | ) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/ui/common/DialogButton.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.ui.common 2 | 3 | import android.view.SoundEffectConstants 4 | import androidx.compose.material3.Text 5 | import androidx.compose.material3.TextButton 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.platform.LocalView 8 | 9 | @Composable 10 | fun DialogButton( 11 | text: String, 12 | onClick: () -> Unit 13 | ) { 14 | val view = LocalView.current 15 | TextButton(onClick = { 16 | view.playSoundEffect(SoundEffectConstants.CLICK) 17 | onClick.invoke() 18 | }) { 19 | Text(text) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/ui/common/FullscreenDialog.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.ui.common 2 | 3 | import androidx.compose.foundation.layout.Box 4 | import androidx.compose.foundation.layout.RowScope 5 | import androidx.compose.foundation.layout.padding 6 | import androidx.compose.material.icons.Icons 7 | import androidx.compose.material.icons.filled.ArrowBack 8 | import androidx.compose.material3.ExperimentalMaterial3Api 9 | import androidx.compose.material3.LargeTopAppBar 10 | import androidx.compose.material3.Scaffold 11 | import androidx.compose.material3.Text 12 | import androidx.compose.material3.TopAppBar 13 | import androidx.compose.material3.TopAppBarDefaults 14 | import androidx.compose.material3.rememberTopAppBarState 15 | import androidx.compose.runtime.Composable 16 | import androidx.compose.ui.Modifier 17 | import androidx.compose.ui.input.nestedscroll.nestedScroll 18 | import androidx.compose.ui.res.stringResource 19 | import androidx.compose.ui.window.Dialog 20 | import androidx.compose.ui.window.DialogProperties 21 | import com.bnyro.recorder.R 22 | 23 | @OptIn(ExperimentalMaterial3Api::class) 24 | @Composable 25 | fun FullscreenDialog( 26 | title: String = "", 27 | onDismissRequest: () -> Unit, 28 | useLargeAppBar: Boolean = false, 29 | actions: @Composable RowScope.() -> Unit = {}, 30 | content: @Composable () -> Unit 31 | ) { 32 | Dialog( 33 | onDismissRequest = onDismissRequest, 34 | properties = DialogProperties( 35 | usePlatformDefaultWidth = false 36 | ) 37 | ) { 38 | val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior( 39 | rememberTopAppBarState() 40 | ) 41 | 42 | Scaffold( 43 | modifier = if (useLargeAppBar) { 44 | Modifier.nestedScroll(scrollBehavior.nestedScrollConnection) 45 | } else { 46 | Modifier 47 | }, 48 | topBar = { 49 | if (useLargeAppBar) { 50 | LargeTopAppBar( 51 | title = { 52 | Text(title) 53 | }, 54 | navigationIcon = { 55 | ClickableIcon( 56 | imageVector = Icons.Default.ArrowBack, 57 | contentDescription = stringResource(R.string.back) 58 | ) { 59 | onDismissRequest.invoke() 60 | } 61 | }, 62 | actions = actions, 63 | scrollBehavior = scrollBehavior 64 | ) 65 | } else { 66 | TopAppBar( 67 | title = { 68 | Text(title) 69 | }, 70 | navigationIcon = { 71 | ClickableIcon( 72 | imageVector = Icons.Default.ArrowBack, 73 | contentDescription = stringResource(R.string.back) 74 | ) { 75 | onDismissRequest.invoke() 76 | } 77 | }, 78 | actions = actions 79 | ) 80 | } 81 | } 82 | ) { pV -> 83 | Box( 84 | modifier = Modifier.padding(pV) 85 | ) { 86 | content.invoke() 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/ui/common/ResponsiveRecordScreenLayout.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.ui.common 2 | 3 | import android.content.res.Configuration 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.foundation.layout.Column 6 | import androidx.compose.foundation.layout.Row 7 | import androidx.compose.foundation.layout.padding 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.ui.Alignment 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.platform.LocalConfiguration 12 | import androidx.compose.ui.unit.dp 13 | 14 | @Composable 15 | fun ResponsiveRecordScreenLayout( 16 | modifier: Modifier = Modifier, 17 | PaneOne: @Composable () -> Unit, 18 | PaneTwo: @Composable () -> Unit 19 | ) { 20 | val isPortrait = LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT 21 | 22 | if (isPortrait) { 23 | Column( 24 | modifier = modifier, 25 | horizontalAlignment = Alignment.CenterHorizontally 26 | ) { 27 | Box(modifier = Modifier.weight(1f)) { 28 | PaneOne() 29 | } 30 | Box(modifier = Modifier.padding(bottom = 20.dp)) { 31 | PaneTwo() 32 | } 33 | } 34 | } else { 35 | Row( 36 | modifier = modifier, 37 | verticalAlignment = Alignment.CenterVertically 38 | ) { 39 | Box(modifier = Modifier.weight(1f)) { 40 | PaneOne() 41 | } 42 | Box(modifier = Modifier.padding(start = 50.dp, end = 30.dp)) { 43 | PaneTwo() 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/ui/common/SelectionDialog.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.ui.common 2 | 3 | import android.view.SoundEffectConstants 4 | import androidx.compose.foundation.clickable 5 | import androidx.compose.foundation.layout.fillMaxWidth 6 | import androidx.compose.foundation.layout.padding 7 | import androidx.compose.foundation.lazy.LazyColumn 8 | import androidx.compose.foundation.lazy.itemsIndexed 9 | import androidx.compose.foundation.shape.RoundedCornerShape 10 | import androidx.compose.material3.AlertDialog 11 | import androidx.compose.material3.Text 12 | import androidx.compose.runtime.Composable 13 | import androidx.compose.ui.Modifier 14 | import androidx.compose.ui.draw.clip 15 | import androidx.compose.ui.platform.LocalView 16 | import androidx.compose.ui.res.stringResource 17 | import androidx.compose.ui.unit.dp 18 | import com.bnyro.recorder.R 19 | 20 | @Composable 21 | fun SelectionDialog( 22 | onDismissRequest: () -> Unit, 23 | title: String, 24 | entries: List, 25 | onSelect: (index: Int) -> Unit 26 | ) { 27 | val view = LocalView.current 28 | AlertDialog( 29 | title = { Text(title) }, 30 | onDismissRequest = onDismissRequest, 31 | confirmButton = { 32 | DialogButton(stringResource(R.string.cancel)) { 33 | onDismissRequest.invoke() 34 | } 35 | }, 36 | text = { 37 | LazyColumn { 38 | itemsIndexed(entries) { index, entry -> 39 | Text( 40 | modifier = Modifier 41 | .fillMaxWidth() 42 | .clip(RoundedCornerShape(20.dp)) 43 | .clickable { 44 | view.playSoundEffect(SoundEffectConstants.CLICK) 45 | onSelect.invoke(index) 46 | onDismissRequest.invoke() 47 | } 48 | .padding(vertical = 12.dp, horizontal = 10.dp), 49 | text = entry 50 | ) 51 | } 52 | } 53 | } 54 | ) 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/ui/components/MiniPlayer.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.ui.components 2 | 3 | import android.view.SoundEffectConstants 4 | import androidx.compose.foundation.layout.Column 5 | import androidx.compose.foundation.layout.Row 6 | import androidx.compose.foundation.layout.Spacer 7 | import androidx.compose.foundation.layout.fillMaxWidth 8 | import androidx.compose.foundation.layout.height 9 | import androidx.compose.foundation.layout.padding 10 | import androidx.compose.material.icons.Icons 11 | import androidx.compose.material.icons.filled.Pause 12 | import androidx.compose.material.icons.filled.PlayArrow 13 | import androidx.compose.material3.ElevatedCard 14 | import androidx.compose.material3.FloatingActionButton 15 | import androidx.compose.material3.Icon 16 | import androidx.compose.material3.Text 17 | import androidx.compose.runtime.Composable 18 | import androidx.compose.runtime.DisposableEffect 19 | import androidx.compose.runtime.getValue 20 | import androidx.compose.runtime.mutableStateOf 21 | import androidx.compose.runtime.remember 22 | import androidx.compose.runtime.setValue 23 | import androidx.compose.ui.Alignment 24 | import androidx.compose.ui.Modifier 25 | import androidx.compose.ui.platform.LocalView 26 | import androidx.compose.ui.res.stringResource 27 | import androidx.compose.ui.unit.dp 28 | import androidx.documentfile.provider.DocumentFile 29 | import androidx.lifecycle.viewmodel.compose.viewModel 30 | import androidx.media3.common.MediaItem 31 | import androidx.media3.common.Player 32 | import com.bnyro.recorder.ui.models.PlayerModel 33 | 34 | @Composable 35 | fun MiniPlayer(inputFile: DocumentFile, playerModel: PlayerModel = viewModel()) { 36 | val view = LocalView.current 37 | DisposableEffect(inputFile) { 38 | with(playerModel.player) { 39 | val mediaItem = MediaItem.Builder().setUri(inputFile.uri).build() 40 | setMediaItem(mediaItem) 41 | playWhenReady = true 42 | prepare() 43 | onDispose { 44 | stop() 45 | } 46 | } 47 | } 48 | ElevatedCard( 49 | modifier = Modifier 50 | .height(140.dp) 51 | .fillMaxWidth() 52 | ) { 53 | Column( 54 | modifier = Modifier 55 | .padding(vertical = 15.dp, horizontal = 15.dp) 56 | ) { 57 | Row( 58 | verticalAlignment = Alignment.CenterVertically 59 | ) { 60 | val fileName = inputFile.name.orEmpty() 61 | Text( 62 | modifier = Modifier.weight(1f), 63 | text = fileName.substringBeforeLast(".").takeIf { 64 | it.isNotBlank() 65 | } ?: fileName 66 | ) 67 | with(playerModel.player) { 68 | 69 | var playState by remember { mutableStateOf(false) } 70 | 71 | DisposableEffect(key1 = this) { 72 | val listener = object : Player.Listener { 73 | override fun onIsPlayingChanged(isPlaying: Boolean) { 74 | playState = isPlaying 75 | } 76 | } 77 | addListener(listener) 78 | onDispose { 79 | removeListener(listener) 80 | } 81 | } 82 | FloatingActionButton( 83 | onClick = { 84 | view.playSoundEffect(SoundEffectConstants.CLICK) 85 | playPause() 86 | } 87 | ) { 88 | if (playState) { 89 | Icon( 90 | Icons.Default.Pause, 91 | contentDescription = stringResource(id = com.bnyro.recorder.R.string.pause) 92 | ) 93 | } else { 94 | Icon( 95 | Icons.Default.PlayArrow, 96 | contentDescription = stringResource(id = com.bnyro.recorder.R.string.play) 97 | ) 98 | } 99 | } 100 | } 101 | } 102 | 103 | Spacer(modifier = Modifier.height(10.dp)) 104 | 105 | PlayerController(exoPlayer = playerModel.player) 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/ui/components/NamingPatternPref.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.ui.components 2 | 3 | import androidx.compose.foundation.clickable 4 | import androidx.compose.foundation.layout.* 5 | import androidx.compose.foundation.shape.RoundedCornerShape 6 | import androidx.compose.material3.AlertDialog 7 | import androidx.compose.material3.OutlinedTextField 8 | import androidx.compose.material3.Text 9 | import androidx.compose.runtime.* 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.draw.clip 12 | import androidx.compose.ui.res.stringResource 13 | import androidx.compose.ui.unit.dp 14 | import androidx.compose.ui.unit.sp 15 | import com.bnyro.recorder.R 16 | import com.bnyro.recorder.ui.common.DialogButton 17 | import com.bnyro.recorder.util.FileRepositoryImpl 18 | import com.bnyro.recorder.util.Preferences 19 | 20 | @Composable 21 | fun NamingPatternPref() { 22 | var showDialog by remember { 23 | mutableStateOf(false) 24 | } 25 | 26 | Column( 27 | modifier = Modifier 28 | .fillMaxWidth() 29 | .clip(RoundedCornerShape(20.dp)) 30 | .clickable { 31 | showDialog = true 32 | } 33 | .padding(horizontal = 2.dp, vertical = 5.dp) 34 | ) { 35 | Text( 36 | text = stringResource(R.string.naming_pattern), 37 | fontSize = 16.sp 38 | ) 39 | Spacer(modifier = Modifier.height(5.dp)) 40 | Text( 41 | text = stringResource(R.string.naming_pattern_desc), 42 | fontSize = 12.sp 43 | ) 44 | } 45 | 46 | if (showDialog) { 47 | var value by remember { 48 | mutableStateOf( 49 | Preferences.getString( 50 | Preferences.namingPatternKey, 51 | FileRepositoryImpl.DEFAULT_NAMING_PATTERN 52 | ) 53 | ) 54 | } 55 | 56 | AlertDialog( 57 | onDismissRequest = { showDialog = false }, 58 | title = { stringResource(R.string.naming_pattern) }, 59 | text = { 60 | OutlinedTextField( 61 | value = value, 62 | onValueChange = { value = it }, 63 | label = { Text(stringResource(R.string.naming_pattern)) } 64 | ) 65 | }, 66 | confirmButton = { 67 | DialogButton(stringResource(R.string.okay)) { 68 | Preferences.edit { putString(Preferences.namingPatternKey, value) } 69 | showDialog = false 70 | } 71 | }, 72 | dismissButton = { 73 | DialogButton(stringResource(R.string.cancel)) { 74 | showDialog = false 75 | } 76 | } 77 | ) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/ui/components/PlayerController.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.ui.components 2 | 3 | import android.text.format.DateUtils 4 | import androidx.compose.foundation.layout.Arrangement 5 | import androidx.compose.foundation.layout.Row 6 | import androidx.compose.foundation.layout.fillMaxWidth 7 | import androidx.compose.material3.Slider 8 | import androidx.compose.material3.Text 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.runtime.State 11 | import androidx.compose.runtime.getValue 12 | import androidx.compose.runtime.mutableStateOf 13 | import androidx.compose.runtime.produceState 14 | import androidx.compose.runtime.remember 15 | import androidx.compose.runtime.setValue 16 | import androidx.compose.ui.Alignment 17 | import androidx.compose.ui.Modifier 18 | import androidx.media3.common.MediaItem 19 | import androidx.media3.common.Player 20 | import androidx.media3.exoplayer.ExoPlayer 21 | import kotlinx.coroutines.delay 22 | import kotlinx.coroutines.isActive 23 | import kotlinx.coroutines.launch 24 | import kotlinx.coroutines.suspendCancellableCoroutine 25 | 26 | @Composable 27 | fun PlayerController(exoPlayer: ExoPlayer) { 28 | with(exoPlayer) { 29 | Row( 30 | modifier = Modifier.fillMaxWidth(), 31 | horizontalArrangement = Arrangement.SpaceEvenly, 32 | verticalAlignment = Alignment.CenterVertically 33 | ) { 34 | val positionAndDuration by positionAndDurationState() 35 | Row(Modifier.weight(1f), verticalAlignment = Alignment.CenterVertically) { 36 | Text(DateUtils.formatElapsedTime(positionAndDuration.first / 1000)) 37 | var tempSliderPosition by remember { mutableStateOf(null) } 38 | Slider( 39 | modifier = Modifier.weight(1f), 40 | value = tempSliderPosition ?: positionAndDuration.first.toFloat(), 41 | onValueChange = { tempSliderPosition = it }, 42 | valueRange = 0f.rangeTo( 43 | positionAndDuration.second?.toFloat() ?: Float.MAX_VALUE 44 | ), 45 | onValueChangeFinished = { 46 | tempSliderPosition?.let { 47 | exoPlayer.seekTo(it.toLong()) 48 | } 49 | tempSliderPosition = null 50 | } 51 | ) 52 | Text( 53 | positionAndDuration.second?.let { DateUtils.formatElapsedTime(it / 1000) } 54 | ?: "" 55 | ) 56 | } 57 | } 58 | } 59 | } 60 | 61 | @Composable 62 | fun Player.positionAndDurationState(): State> { 63 | return produceState( 64 | initialValue = (currentPosition to duration.let { if (it < 0) null else it }), 65 | this 66 | ) { 67 | var isSeeking = false 68 | val listener = object : Player.Listener { 69 | override fun onPlaybackStateChanged(playbackState: Int) { 70 | if (playbackState == Player.STATE_READY) { 71 | isSeeking = false 72 | } 73 | } 74 | 75 | override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) { 76 | value = currentPosition to value.second 77 | } 78 | 79 | override fun onPositionDiscontinuity( 80 | oldPosition: Player.PositionInfo, 81 | newPosition: Player.PositionInfo, 82 | reason: Int 83 | ) { 84 | if (reason == Player.DISCONTINUITY_REASON_SEEK) { 85 | isSeeking = true 86 | value = currentPosition to duration.let { if (it < 0) null else it } 87 | } 88 | } 89 | } 90 | addListener(listener) 91 | 92 | val pollJob = launch { 93 | while (isActive) { 94 | delay(1000) 95 | if (!isSeeking) { 96 | value = currentPosition to duration.let { if (it < 0) null else it } 97 | } 98 | } 99 | } 100 | try { 101 | suspendCancellableCoroutine { } 102 | } finally { 103 | pollJob.cancel() 104 | removeListener(listener) 105 | } 106 | } 107 | } 108 | 109 | fun Player.playPause() { 110 | if (isPlaying) pause() else play() 111 | } 112 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/ui/components/PlayerView.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.ui.components 2 | 3 | import android.view.SoundEffectConstants 4 | import androidx.compose.foundation.ExperimentalFoundationApi 5 | import androidx.compose.foundation.layout.Column 6 | import androidx.compose.foundation.layout.fillMaxSize 7 | import androidx.compose.foundation.layout.fillMaxWidth 8 | import androidx.compose.foundation.layout.padding 9 | import androidx.compose.foundation.pager.HorizontalPager 10 | import androidx.compose.foundation.pager.rememberPagerState 11 | import androidx.compose.material3.MaterialTheme 12 | import androidx.compose.material3.Tab 13 | import androidx.compose.material3.TabRow 14 | import androidx.compose.material3.Text 15 | import androidx.compose.runtime.* 16 | import androidx.compose.ui.Modifier 17 | import androidx.compose.ui.platform.LocalView 18 | import androidx.compose.ui.res.stringResource 19 | import androidx.compose.ui.unit.dp 20 | import androidx.lifecycle.viewmodel.compose.viewModel 21 | import com.bnyro.recorder.R 22 | import com.bnyro.recorder.ui.models.PlayerModel 23 | import kotlinx.coroutines.launch 24 | 25 | @OptIn(ExperimentalFoundationApi::class) 26 | @Composable 27 | fun PlayerView( 28 | showVideoModeInitially: Boolean 29 | ) { 30 | val playerModel: PlayerModel = viewModel(factory = PlayerModel.Factory) 31 | 32 | Column( 33 | modifier = Modifier.fillMaxSize() 34 | ) { 35 | val pagerState = rememberPagerState( 36 | initialPage = if (showVideoModeInitially) 1 else 0, 37 | initialPageOffsetFraction = 0f 38 | ) { 39 | 2 40 | } 41 | val view = LocalView.current 42 | val scope = rememberCoroutineScope() 43 | TabRow(selectedTabIndex = pagerState.currentPage, Modifier.fillMaxWidth()) { 44 | Tab( 45 | selected = pagerState.currentPage == 0, 46 | onClick = { 47 | view.playSoundEffect(SoundEffectConstants.CLICK) 48 | scope.launch { 49 | pagerState.animateScrollToPage(0) 50 | } 51 | } 52 | ) { 53 | Text( 54 | stringResource(R.string.audio), 55 | Modifier.padding(10.dp), 56 | style = MaterialTheme.typography.titleMedium 57 | ) 58 | } 59 | Tab( 60 | selected = pagerState.currentPage == 1, 61 | onClick = { 62 | view.playSoundEffect(SoundEffectConstants.CLICK) 63 | scope.launch { 64 | pagerState.animateScrollToPage(1) 65 | } 66 | } 67 | ) { 68 | Text( 69 | text = stringResource(R.string.video), 70 | Modifier.padding(10.dp), 71 | style = MaterialTheme.typography.titleMedium 72 | ) 73 | } 74 | } 75 | 76 | HorizontalPager( 77 | state = pagerState, 78 | modifier = Modifier.fillMaxSize() 79 | ) { index -> 80 | when (index) { 81 | 0 -> RecordingItemList( 82 | items = playerModel.audioRecordingItems, 83 | isVideoList = false 84 | ) 85 | 86 | 1 -> RecordingItemList( 87 | items = playerModel.screenRecordingItems, 88 | isVideoList = true 89 | ) 90 | } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/ui/components/RecorderPreview.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.ui.components 2 | 3 | import androidx.activity.ComponentActivity 4 | import androidx.compose.animation.Crossfade 5 | import androidx.compose.foundation.layout.fillMaxSize 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Modifier 8 | import androidx.compose.ui.platform.LocalContext 9 | import androidx.lifecycle.viewmodel.compose.viewModel 10 | import com.bnyro.recorder.R 11 | import com.bnyro.recorder.ui.common.BlobIconBox 12 | import com.bnyro.recorder.ui.models.RecorderModel 13 | 14 | @Composable 15 | fun RecorderPreview(recordScreenMode: Boolean) { 16 | val recorderModel: RecorderModel = viewModel(LocalContext.current as ComponentActivity) 17 | if (recordScreenMode) { 18 | BlobIconBox( 19 | icon = R.drawable.ic_screen_record 20 | ) 21 | } else { 22 | Crossfade( 23 | modifier = Modifier.fillMaxSize(), 24 | targetState = recorderModel.recordedAmplitudes 25 | ) { 26 | when (it.isEmpty()) { 27 | true -> BlobIconBox( 28 | icon = R.drawable.ic_mic 29 | ) 30 | 31 | false -> AudioVisualizer( 32 | modifier = Modifier 33 | .fillMaxSize() 34 | ) 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/ui/dialogs/ConfirmationDialog.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.ui.dialogs 2 | 3 | import androidx.annotation.StringRes 4 | import androidx.compose.material3.AlertDialog 5 | import androidx.compose.material3.Text 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.res.stringResource 8 | import com.bnyro.recorder.R 9 | import com.bnyro.recorder.ui.common.DialogButton 10 | 11 | @Composable 12 | fun ConfirmationDialog( 13 | @StringRes title: Int, 14 | onDismissRequest: () -> Unit, 15 | onConfirm: () -> Unit 16 | ) { 17 | AlertDialog( 18 | onDismissRequest = onDismissRequest, 19 | title = { 20 | Text(stringResource(title)) 21 | }, 22 | text = { 23 | Text(stringResource(R.string.irreversible)) 24 | }, 25 | confirmButton = { 26 | DialogButton(stringResource(R.string.okay)) { 27 | onConfirm.invoke() 28 | onDismissRequest.invoke() 29 | } 30 | }, 31 | dismissButton = { 32 | DialogButton(stringResource(R.string.cancel)) { 33 | onDismissRequest.invoke() 34 | } 35 | } 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/ui/models/PlayerModel.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.ui.models 2 | 3 | import android.content.Context 4 | import androidx.compose.runtime.getValue 5 | import androidx.compose.runtime.mutableStateOf 6 | import androidx.compose.runtime.setValue 7 | import androidx.documentfile.provider.DocumentFile 8 | import androidx.lifecycle.ViewModel 9 | import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY 10 | import androidx.lifecycle.viewModelScope 11 | import androidx.lifecycle.viewmodel.initializer 12 | import androidx.lifecycle.viewmodel.viewModelFactory 13 | import androidx.media3.common.util.UnstableApi 14 | import androidx.media3.exoplayer.ExoPlayer 15 | import com.bnyro.recorder.App 16 | import com.bnyro.recorder.enums.SortOrder 17 | import com.bnyro.recorder.obj.RecordingItemData 18 | import com.bnyro.recorder.util.FileRepository 19 | import kotlinx.coroutines.launch 20 | 21 | class PlayerModel(context: Context, private val fileRepository: FileRepository) : ViewModel() { 22 | @UnstableApi 23 | var player = ExoPlayer.Builder(context) 24 | .setUsePlatformDiagnostics(false) 25 | .build() 26 | 27 | var selectedFiles by mutableStateOf(listOf()) 28 | 29 | private var sortOrder = SortOrder.MODIFIED 30 | 31 | var audioRecordingItems by mutableStateOf(listOf()) 32 | var screenRecordingItems by mutableStateOf(listOf()) 33 | 34 | init { 35 | loadFiles() 36 | } 37 | 38 | fun loadFiles() { 39 | viewModelScope.launch { 40 | audioRecordingItems = fileRepository.getAudioRecordingItems(sortOrder) 41 | screenRecordingItems = fileRepository.getVideoRecordingItems(sortOrder) 42 | } 43 | } 44 | 45 | fun sortItems(newSortOrder: SortOrder) { 46 | if (newSortOrder == sortOrder) return 47 | sortOrder = newSortOrder 48 | loadFiles() 49 | } 50 | 51 | fun deleteFiles() { 52 | viewModelScope.launch { 53 | if (selectedFiles.isEmpty()) { 54 | fileRepository.deleteAllFiles() 55 | loadFiles() 56 | return@launch 57 | } 58 | fileRepository.deleteFiles(selectedFiles.map { it.recordingFile }) 59 | selectedFiles = emptyList() 60 | loadFiles() 61 | } 62 | } 63 | 64 | fun stopPlaying() { 65 | player.stop() 66 | } 67 | 68 | companion object { 69 | val Factory = viewModelFactory { 70 | initializer { 71 | val application = (this[APPLICATION_KEY] as App) 72 | PlayerModel(application, application.fileRepository) 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/ui/models/ThemeModel.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.ui.models 2 | 3 | import androidx.compose.runtime.getValue 4 | import androidx.compose.runtime.mutableStateOf 5 | import androidx.compose.runtime.setValue 6 | import androidx.lifecycle.ViewModel 7 | import com.bnyro.recorder.enums.ThemeMode 8 | 9 | class ThemeModel : ViewModel() { 10 | var themeMode by mutableStateOf( 11 | ThemeMode.getCurrent() 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/ui/models/TrimmerModel.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.ui.models 2 | 3 | import android.content.Context 4 | import android.os.Build 5 | import androidx.annotation.RequiresApi 6 | import androidx.compose.runtime.getValue 7 | import androidx.compose.runtime.mutableLongStateOf 8 | import androidx.compose.runtime.mutableStateOf 9 | import androidx.compose.runtime.setValue 10 | import androidx.documentfile.provider.DocumentFile 11 | import androidx.lifecycle.ViewModel 12 | import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY 13 | import androidx.lifecycle.viewModelScope 14 | import androidx.lifecycle.viewmodel.initializer 15 | import androidx.lifecycle.viewmodel.viewModelFactory 16 | import androidx.media3.common.util.UnstableApi 17 | import androidx.media3.exoplayer.ExoPlayer 18 | import com.bnyro.recorder.App 19 | import com.bnyro.recorder.enums.TrimmerState 20 | import com.bnyro.recorder.util.MediaTrimmer 21 | import kotlinx.coroutines.launch 22 | 23 | class TrimmerModel(context: Context) : ViewModel() { 24 | 25 | @UnstableApi 26 | val player = ExoPlayer.Builder(context) 27 | .setUsePlatformDiagnostics(false) 28 | .build() 29 | 30 | var startTimeStamp by mutableLongStateOf(0L) 31 | var endTimeStamp by mutableStateOf(null) 32 | 33 | var trimmerState: TrimmerState by mutableStateOf(TrimmerState.NoJob) 34 | 35 | @RequiresApi(Build.VERSION_CODES.O) 36 | fun startTrimmer(context: Context, inputFile: DocumentFile) { 37 | val endT = endTimeStamp ?: return 38 | if (endT <= startTimeStamp) return 39 | viewModelScope.launch { 40 | trimmerState = TrimmerState.Running 41 | val trimmer = MediaTrimmer() 42 | val result = trimmer.trimMedia(context, inputFile, startTimeStamp, endT) 43 | trimmerState = if (result) { 44 | TrimmerState.Success 45 | } else { 46 | TrimmerState.Failed 47 | } 48 | } 49 | } 50 | 51 | companion object { 52 | val Factory = viewModelFactory { 53 | initializer { 54 | val application = 55 | (this[APPLICATION_KEY] as App) 56 | TrimmerModel(application) 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/ui/screens/RecorderScreen.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.ui.screens 2 | 3 | import androidx.activity.ComponentActivity 4 | import androidx.compose.foundation.layout.fillMaxSize 5 | import androidx.compose.foundation.layout.padding 6 | import androidx.compose.material3.Scaffold 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.runtime.LaunchedEffect 9 | import androidx.compose.ui.Modifier 10 | import androidx.compose.ui.platform.LocalContext 11 | import androidx.lifecycle.viewmodel.compose.viewModel 12 | import com.bnyro.recorder.enums.RecorderState 13 | import com.bnyro.recorder.ui.common.ResponsiveRecordScreenLayout 14 | import com.bnyro.recorder.ui.components.RecorderController 15 | import com.bnyro.recorder.ui.components.RecorderPreview 16 | import com.bnyro.recorder.ui.models.RecorderModel 17 | 18 | @Composable 19 | fun RecorderView( 20 | recordScreenMode: Boolean 21 | ) { 22 | val recorderModel: RecorderModel = viewModel(LocalContext.current as ComponentActivity) 23 | 24 | LaunchedEffect(recorderModel.recorderState) { 25 | // update the UI when the recorder gets destroyed by the notification 26 | if (recorderModel.recorderState == RecorderState.IDLE) { 27 | recorderModel.stopRecording() 28 | } 29 | } 30 | 31 | Scaffold { pV -> 32 | ResponsiveRecordScreenLayout( 33 | modifier = Modifier 34 | .fillMaxSize() 35 | .padding(pV), 36 | PaneOne = { 37 | RecorderPreview(recordScreenMode) 38 | }, 39 | PaneTwo = { 40 | RecorderController(recordScreenMode) 41 | } 42 | ) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.ui.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val Purple80 = Color(0xFFD0BCFF) 6 | val PurpleGrey80 = Color(0xFFCCC2DC) 7 | val Pink80 = Color(0xFFEFB8C8) 8 | 9 | val Purple40 = Color(0xFF6650a4) 10 | val PurpleGrey40 = Color(0xFF625b71) 11 | val Pink40 = Color(0xFF7D5260) 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.ui.theme 2 | 3 | import android.app.Activity 4 | import android.os.Build 5 | import androidx.compose.foundation.isSystemInDarkTheme 6 | import androidx.compose.material3.MaterialTheme 7 | import androidx.compose.material3.darkColorScheme 8 | import androidx.compose.material3.dynamicDarkColorScheme 9 | import androidx.compose.material3.dynamicLightColorScheme 10 | import androidx.compose.material3.lightColorScheme 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.runtime.SideEffect 13 | import androidx.compose.ui.graphics.Color 14 | import androidx.compose.ui.platform.LocalContext 15 | import androidx.compose.ui.platform.LocalView 16 | import androidx.core.view.WindowCompat 17 | 18 | private val DarkColorScheme = darkColorScheme( 19 | primary = Purple80, 20 | secondary = PurpleGrey80, 21 | tertiary = Pink80 22 | ) 23 | 24 | private val LightColorScheme = lightColorScheme( 25 | primary = Purple40, 26 | secondary = PurpleGrey40, 27 | tertiary = Pink40 28 | 29 | /* Other default colors to override 30 | background = Color(0xFFFFFBFE), 31 | surface = Color(0xFFFFFBFE), 32 | onPrimary = Color.White, 33 | onSecondary = Color.White, 34 | onTertiary = Color.White, 35 | onBackground = Color(0xFF1C1B1F), 36 | onSurface = Color(0xFF1C1B1F), 37 | */ 38 | ) 39 | 40 | private val AmoledDarkColorScheme = darkColorScheme( 41 | primary = Color(0xFFEE665B), 42 | background = Color(0xFF000000), 43 | onPrimary = Color(0xFFFFFFFF) 44 | ) 45 | 46 | @Composable 47 | fun RecordYouTheme( 48 | darkTheme: Boolean = isSystemInDarkTheme(), 49 | // Dynamic color is available on Android 12+ 50 | dynamicColor: Boolean = true, 51 | amoledDark: Boolean = false, 52 | content: @Composable () -> Unit 53 | ) { 54 | val colorScheme = when { 55 | amoledDark && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { 56 | val context = LocalContext.current 57 | dynamicDarkColorScheme(context).copy(background = Color.Black) 58 | } 59 | 60 | amoledDark -> AmoledDarkColorScheme 61 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { 62 | val context = LocalContext.current 63 | if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) 64 | } 65 | 66 | darkTheme -> DarkColorScheme 67 | else -> LightColorScheme 68 | } 69 | val view = LocalView.current 70 | if (!view.isInEditMode) { 71 | SideEffect { 72 | val activity = view.context as Activity 73 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 74 | WindowCompat.getInsetsController( 75 | activity.window, 76 | view 77 | ).isAppearanceLightStatusBars = !darkTheme 78 | WindowCompat.getInsetsController( 79 | activity.window, 80 | view 81 | ).isAppearanceLightNavigationBars = !darkTheme 82 | } 83 | } 84 | } 85 | 86 | MaterialTheme( 87 | colorScheme = colorScheme, 88 | typography = Typography, 89 | content = content 90 | ) 91 | } 92 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.ui.theme 2 | 3 | import androidx.compose.material3.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.FontFamily 6 | import androidx.compose.ui.text.font.FontWeight 7 | import androidx.compose.ui.unit.sp 8 | 9 | // Set of Material typography styles to start with 10 | val Typography = Typography( 11 | bodyLarge = TextStyle( 12 | fontFamily = FontFamily.Default, 13 | fontWeight = FontWeight.Normal, 14 | fontSize = 16.sp, 15 | lineHeight = 24.sp, 16 | letterSpacing = 0.5.sp 17 | ) 18 | /* Other default text styles to override 19 | titleLarge = TextStyle( 20 | fontFamily = FontFamily.Default, 21 | fontWeight = FontWeight.Normal, 22 | fontSize = 22.sp, 23 | lineHeight = 28.sp, 24 | letterSpacing = 0.sp 25 | ), 26 | labelSmall = TextStyle( 27 | fontFamily = FontFamily.Default, 28 | fontWeight = FontWeight.Medium, 29 | fontSize = 11.sp, 30 | lineHeight = 16.sp, 31 | letterSpacing = 0.5.sp 32 | ) 33 | */ 34 | ) 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/ui/views/VideoView.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.ui.views 2 | 3 | import android.net.Uri 4 | import androidx.annotation.OptIn 5 | import androidx.compose.foundation.layout.fillMaxSize 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.runtime.DisposableEffect 8 | import androidx.compose.runtime.remember 9 | import androidx.compose.ui.Modifier 10 | import androidx.compose.ui.platform.LocalContext 11 | import androidx.compose.ui.viewinterop.AndroidView 12 | import androidx.media3.common.MediaItem 13 | import androidx.media3.common.util.UnstableApi 14 | import androidx.media3.exoplayer.ExoPlayer 15 | import androidx.media3.ui.PlayerView 16 | 17 | @OptIn(UnstableApi::class) 18 | @Composable 19 | fun VideoView(videoUri: Uri) { 20 | val context = LocalContext.current 21 | 22 | val exoPlayer = remember(context) { 23 | ExoPlayer.Builder(context) 24 | .setUsePlatformDiagnostics(false) 25 | .build() 26 | .also { exoPlayer -> 27 | val mediaItem = MediaItem.Builder() 28 | .setUri(videoUri) 29 | .build() 30 | exoPlayer.setMediaItem(mediaItem) 31 | exoPlayer.prepare() 32 | exoPlayer.playWhenReady = true 33 | } 34 | } 35 | DisposableEffect( 36 | AndroidView( 37 | modifier = Modifier.fillMaxSize(), 38 | factory = { 39 | PlayerView(context).apply { 40 | player = exoPlayer 41 | setShowNextButton(false) 42 | setShowPreviousButton(false) 43 | } 44 | } 45 | ) 46 | ) { 47 | onDispose { exoPlayer.release() } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/util/IntentHelper.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.util 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.net.Uri 6 | import android.util.Log 7 | import android.widget.Toast 8 | import androidx.core.content.FileProvider 9 | import androidx.core.net.toUri 10 | import androidx.documentfile.provider.DocumentFile 11 | import com.bnyro.recorder.R 12 | import java.io.File 13 | 14 | object IntentHelper { 15 | fun openFile(context: Context, file: DocumentFile) { 16 | val uri = getFileUri(context, file) 17 | val target = Intent().apply { 18 | action = Intent.ACTION_VIEW 19 | addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) 20 | setDataAndType(uri, context.contentResolver.getType(uri)) 21 | } 22 | val chooser = Intent.createChooser(target, context.getString(R.string.share)) 23 | startActivity(context, chooser) 24 | } 25 | 26 | fun shareFile(context: Context, file: DocumentFile) { 27 | val uri = getFileUri(context, file) 28 | val target = Intent().apply { 29 | action = Intent.ACTION_SEND 30 | addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) 31 | type = context.contentResolver.getType(uri) 32 | putExtra(Intent.EXTRA_STREAM, uri) 33 | } 34 | val chooser = Intent.createChooser(target, context.getString(R.string.share)) 35 | startActivity(context, chooser) 36 | } 37 | 38 | private fun getFileUri(context: Context, file: DocumentFile): Uri { 39 | return if (file.uri.path!!.startsWith( 40 | (context.getExternalFilesDir(null) ?: context.filesDir).path 41 | ) 42 | ) { 43 | val rawFile = File( 44 | context.getExternalFilesDir(null) ?: context.filesDir, 45 | file.name.orEmpty() 46 | ) 47 | Log.e("using raw", rawFile.absolutePath) 48 | FileProvider.getUriForFile(context, context.packageName + ".provider", rawFile) 49 | } else { 50 | file.uri 51 | } 52 | 53 | } 54 | 55 | fun openHref(context: Context, url: String) { 56 | val browserIntent = Intent(Intent.ACTION_VIEW, url.toUri()) 57 | startActivity(context, browserIntent) 58 | } 59 | 60 | private fun startActivity(context: Context, intent: Intent) { 61 | try { 62 | context.startActivity(intent) 63 | } catch (e: Exception) { 64 | Toast.makeText(context, e.localizedMessage, Toast.LENGTH_SHORT).show() 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/util/NotificationHelper.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.util 2 | 3 | import android.content.Context 4 | import androidx.core.app.NotificationChannelCompat 5 | import androidx.core.app.NotificationManagerCompat 6 | import com.bnyro.recorder.R 7 | 8 | object NotificationHelper { 9 | const val RECORDING_NOTIFICATION_CHANNEL = "active_recording" 10 | const val RECORDING_FINISHED_N_CHANNEL = "recording_finished" 11 | const val RECORDING_NOTIFICATION_ID = 1 12 | const val RECORDING_FINISHED_N_ID = 2 13 | 14 | fun buildNotificationChannels(context: Context) { 15 | val notificationManager = NotificationManagerCompat.from(context) 16 | 17 | listOf( 18 | RECORDING_NOTIFICATION_CHANNEL to R.string.active_recording, 19 | RECORDING_FINISHED_N_CHANNEL to R.string.recording_finished 20 | ).forEach { (channelName, stringResource) -> 21 | val channelCompat = NotificationChannelCompat.Builder( 22 | channelName, 23 | NotificationManagerCompat.IMPORTANCE_HIGH 24 | ) 25 | .setName(context.getString(stringResource)) 26 | .setLightsEnabled(true) 27 | .setShowBadge(true) 28 | .setVibrationEnabled(true) 29 | .build() 30 | 31 | notificationManager.createNotificationChannel(channelCompat) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/util/PcmConverter.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.util 2 | 3 | import android.util.Log 4 | import java.io.IOException 5 | import java.io.InputStream 6 | import java.io.OutputStream 7 | 8 | class PcmConverter(sampleRate: Long, channels: Int, bitsPerSample: Int) { 9 | private val byteRate = channels * sampleRate * bitsPerSample / 8 10 | 11 | private val wavHeader = byteArrayOf( 12 | 'R'.code.toByte(), 13 | 'I'.code.toByte(), 14 | 'F'.code.toByte(), 15 | 'F'.code.toByte(), 16 | 0, 17 | 0, 18 | 0, 19 | 0, // data length placeholder 20 | 'W'.code.toByte(), 21 | 'A'.code.toByte(), 22 | 'V'.code.toByte(), 23 | 'E'.code.toByte(), 24 | 'f'.code.toByte(), 25 | 'm'.code.toByte(), 26 | 't'.code.toByte(), 27 | ' '.code.toByte(), // 'fmt ' chunk 28 | 16, // 4 bytes: size of 'fmt ' chunk 29 | 0, 30 | 0, 31 | 0, 32 | 1, // format = 1 33 | 0, 34 | channels.toByte(), 35 | 0, 36 | (sampleRate and 0xffL).toByte(), 37 | (sampleRate shr 8 and 0xffL).toByte(), 38 | 0, 39 | 0, 40 | (byteRate and 0xffL).toByte(), 41 | (byteRate shr 8 and 0xffL).toByte(), 42 | (byteRate shr 16 and 0xffL).toByte(), 43 | 0, 44 | (channels * bitsPerSample / 8).toByte(), // block align 45 | 0, 46 | bitsPerSample.toByte(), // bits per sample 47 | 0, 48 | 'd'.code.toByte(), 49 | 'a'.code.toByte(), 50 | 't'.code.toByte(), 51 | 'a'.code.toByte(), 52 | 0, 53 | 0, 54 | 0, 55 | 0 56 | ) 57 | 58 | fun convertToWave(inputStream: InputStream, outputStream: OutputStream, bufferSize: Int) { 59 | val data = ByteArray(bufferSize) 60 | try { 61 | val audioLength = inputStream.available().toLong() 62 | val dataLength = audioLength + 36 63 | writeWaveHeader(outputStream, audioLength, dataLength) 64 | while (inputStream.read(data) != -1) { 65 | outputStream.write(data) 66 | } 67 | } catch (e: IOException) { 68 | Log.e(TAG, "Failed to convert to wav", e) 69 | } finally { 70 | inputStream.close() 71 | outputStream.close() 72 | } 73 | } 74 | 75 | // http://stackoverflow.com/questions/4440015/java-pcm-to-wav 76 | @Throws(IOException::class) 77 | private fun writeWaveHeader( 78 | out: OutputStream?, 79 | audioLength: Long, 80 | dataLength: Long 81 | ) { 82 | val header = wavHeader.copyOf(wavHeader.size) 83 | header[4] = (dataLength and 0xffL).toByte() 84 | header[5] = (dataLength shr 8 and 0xffL).toByte() 85 | header[6] = (dataLength shr 16 and 0xffL).toByte() 86 | header[7] = (dataLength shr 24 and 0xffL).toByte() 87 | header[40] = (audioLength and 0xffL).toByte() 88 | header[41] = (audioLength shr 8 and 0xffL).toByte() 89 | header[42] = (audioLength shr 16 and 0xffL).toByte() 90 | header[43] = (audioLength shr 24 and 0xffL).toByte() 91 | out!!.write(header, 0, header.size) 92 | } 93 | 94 | companion object { 95 | private const val TAG = "PcmConverter" 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/util/PermissionHelper.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.util 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.content.pm.PackageManager 6 | import androidx.core.app.ActivityCompat 7 | 8 | object PermissionHelper { 9 | fun checkPermissions(context: Context, permissions: Array): Boolean { 10 | permissions.forEach { 11 | if (!hasPermission(context, it)) { 12 | ActivityCompat.requestPermissions( 13 | context as Activity, 14 | arrayOf(it), 15 | 1 16 | ) 17 | return false 18 | } 19 | } 20 | return true 21 | } 22 | 23 | fun hasPermission(context: Context, permission: String): Boolean { 24 | return ActivityCompat.checkSelfPermission( 25 | context, 26 | permission 27 | ) == PackageManager.PERMISSION_GRANTED 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/util/PickFolderContract.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.util 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.net.Uri 7 | import android.os.Build 8 | import android.provider.DocumentsContract 9 | import androidx.activity.result.contract.ActivityResultContract 10 | import androidx.annotation.CallSuper 11 | 12 | class PickFolderContract : ActivityResultContract() { 13 | private lateinit var context: Context 14 | 15 | @CallSuper 16 | override fun createIntent(context: Context, input: Uri?): Intent { 17 | this.context = context 18 | 19 | val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply { 20 | flags = Intent.FLAG_GRANT_WRITE_URI_PERMISSION or 21 | Intent.FLAG_GRANT_READ_URI_PERMISSION or 22 | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION 23 | } 24 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && input != null) { 25 | intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, input) 26 | } 27 | return intent 28 | } 29 | 30 | override fun getSynchronousResult( 31 | context: Context, 32 | input: Uri? 33 | ): SynchronousResult? = null 34 | 35 | override fun parseResult(resultCode: Int, intent: Intent?): Uri? { 36 | return intent.takeIf { resultCode == Activity.RESULT_OK }?.data?.also { 37 | val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION 38 | context.contentResolver.takePersistableUriPermission(it, flags) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/util/PlayerHelper.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.util 2 | 3 | import android.content.Context 4 | import android.media.AudioAttributes 5 | import android.media.MediaRecorder 6 | import android.os.Build 7 | 8 | object PlayerHelper { 9 | fun newRecorder(context: Context): MediaRecorder { 10 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { 11 | MediaRecorder(context) 12 | } else { 13 | @Suppress("DEPRECATION") 14 | (MediaRecorder()) 15 | } 16 | } 17 | fun getAudioAttributes(): AudioAttributes = AudioAttributes.Builder() 18 | .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) 19 | .setUsage(AudioAttributes.USAGE_MEDIA) 20 | .build() 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/util/Preferences.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.util 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | 6 | object Preferences { 7 | private const val PREF_FILE_NAME = "RecordYou" 8 | lateinit var prefs: SharedPreferences 9 | 10 | const val targetFolderKey = "targetFolder" 11 | const val audioFormatKey = "audioFormat" 12 | const val audioSourceKey = "audioSource" 13 | const val audioSampleRateKey = "audioSampleRate" 14 | const val audioBitrateKey = "audioBitrate" 15 | const val audioChannelsKey = "audioChannels" 16 | const val audioDeviceSourceKey = "audioDeviceSource" 17 | const val videoCodecKey = "videoCodec" 18 | const val videoBitrateKey = "videoBitrate" 19 | const val themeModeKey = "themeMode" 20 | const val losslessRecorderKey = "losslessRecorder" 21 | const val namingPatternKey = "namingPattern" 22 | const val showOverlayAnnotationToolKey = "annotationTool" 23 | const val showVisualizerTimestamps = "visualizerTimestamp" 24 | 25 | fun init(context: Context) { 26 | prefs = context.getSharedPreferences(PREF_FILE_NAME, Context.MODE_PRIVATE) 27 | } 28 | 29 | fun edit(action: SharedPreferences.Editor.() -> Unit) { 30 | prefs.edit().apply(action).apply() 31 | } 32 | 33 | fun getString(key: String, defValue: String) = prefs.getString(key, defValue) ?: defValue 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/bnyro/recorder/util/ShortcutHelper.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder.util 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import androidx.annotation.DrawableRes 6 | import androidx.annotation.StringRes 7 | import androidx.core.content.pm.ShortcutInfoCompat 8 | import androidx.core.content.pm.ShortcutManagerCompat 9 | import androidx.core.graphics.drawable.IconCompat 10 | import com.bnyro.recorder.R 11 | import com.bnyro.recorder.enums.RecorderType 12 | import com.bnyro.recorder.ui.MainActivity 13 | 14 | object ShortcutHelper { 15 | sealed class AppShortcut( 16 | val action: String, 17 | @DrawableRes val iconRes: Int, 18 | @StringRes val label: Int 19 | ) { 20 | object RecordAudio : AppShortcut(RecorderType.AUDIO.name, R.drawable.ic_audio, R.string.record_sound) 21 | object RecordScreen : AppShortcut(RecorderType.VIDEO.name, R.drawable.ic_screen, R.string.record_screen) 22 | } 23 | private val shortcuts = listOf(AppShortcut.RecordAudio, AppShortcut.RecordScreen) 24 | 25 | private fun createShortcut(context: Context, action: String, label: String, icon: IconCompat) { 26 | val shortcut = ShortcutInfoCompat.Builder(context, action) 27 | .setShortLabel(label) 28 | .setLongLabel(label) 29 | .setIcon(icon) 30 | .setIntent( 31 | Intent(context, MainActivity::class.java).apply { 32 | this.action = Intent.ACTION_VIEW 33 | putExtra(MainActivity.EXTRA_ACTION_KEY, action) 34 | } 35 | ) 36 | .build() 37 | 38 | ShortcutManagerCompat.pushDynamicShortcut(context, shortcut) 39 | } 40 | 41 | fun createShortcuts(context: Context) { 42 | ShortcutManagerCompat.getDynamicShortcuts(context).takeIf { it.isEmpty() } ?: return 43 | 44 | shortcuts.forEach { 45 | createShortcut( 46 | context, 47 | it.action, 48 | context.getString(it.label), 49 | IconCompat.createWithResource(context, it.iconRes) 50 | ) 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 14 | 15 | 21 | 22 | 23 | 24 | 25 | 26 | 28 | 29 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_monochrome.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 14 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/blob.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_audio.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_eraser_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_mic.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 14 | 21 | 28 | 35 | 42 | 49 | 56 | 63 | 64 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_notification.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_screen.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_screen_record.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 14 | 21 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/you-apps/RecordYou/2b44e4688f29b0b6d533c3012b26283e0789c10d/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/you-apps/RecordYou/2b44e4688f29b0b6d533c3012b26283e0789c10d/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/you-apps/RecordYou/2b44e4688f29b0b6d533c3012b26283e0789c10d/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/you-apps/RecordYou/2b44e4688f29b0b6d533c3012b26283e0789c10d/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/you-apps/RecordYou/2b44e4688f29b0b6d533c3012b26283e0789c10d/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-ar/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | تنسيق الصوت 4 | التسجيلات 5 | حسنًا 6 | اسم الملف 7 | تسجيل الصوت 8 | إلغاء 9 | إعادة تسمية 10 | حذف 11 | هل أنت متأكد؟ هذا لا يمكن التراجع عنه! 12 | لا شيء هنا. 13 | ميكروفون 14 | مسجل الشاشة 15 | تسجيل الشاشة 16 | صوت 17 | يتم تسجيل الشاشة … 18 | الدليل 19 | إيقاف 20 | شارك 21 | التسجيل النشط 22 | ايقاف 23 | اختر مجلَّدًا 24 | تسجيل الصوت … 25 | لا يوجد صوت 26 | افتح 27 | فيديو 28 | إستئناف 29 | حول التطبيق 30 | تم الانتهاء من التسجيل 31 | الاعدادات 32 | تسجبل 33 | تشغيل 34 | خيارات 35 | العودة 36 | الكود المصدري 37 | المؤلِّف 38 | الترجمة 39 | حذف الكل 40 | معدل البت 41 | تلقائيّ 42 | السمة 43 | داكن 44 | مظيء 45 | النظام 46 | معدل العينة 47 | الإفتراضي 48 | كاميرا الفيديو 49 | ستيريو 50 | غير معالج 51 | احاديه 52 | مسجل الصوت بدون فقدان البيانات (WAV) 53 | لاحظ أنه لن يتم تطبيق خيارات الصوت الأخرى المحددة أثناء الاستخدام. 54 | أبجدي (معكوس) 55 | الحجم 56 | الحجم (معكوس) 57 | %d: التاريخ، %t: الوقت، %s: ثواني العصر، %m: الحقبة المللية 58 | فرز 59 | أبجدي 60 | نمط التسمية 61 | تعليق توضيحي لسجل الشاشة 62 | إظهار أداة التعليق التوضيحي أثناء تسجيل الشاشة 63 | اسود 64 | الإفتراضي 65 | حدد نطاق القص 66 | قيد القص… 67 | قص 68 | يبدأ: 69 | لا يمكن الوصول إلى المجلد المحدد! 70 | الطوابع الزمنية لمعادل الصوت 71 | إظهار الطوابع الزمنية في المخطط الزمني للمعادل الصوتي. 72 | قف: 73 | لم يتم تعيينه 74 | تم القص بنجاح 75 | ابدأ القص 76 | فشل القص 77 | وضع الرسم 78 | وضع الممحاة 79 | تم تعديله 80 | تم تعديله(معكوس) 81 | الصوت الداخلي (الجذر) 82 | لا توجد أذونات كافية لبدء التسجيل 83 | -------------------------------------------------------------------------------- /app/src/main/res/values-az/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Format 4 | Səsyazmalar 5 | Oldu 6 | Ləğv et 7 | Adı dəyiş 8 | Fayl adı 9 | Bura boşdur 10 | Səs yaz 11 | Kataloq 12 | Kataloq seç 13 | Sil 14 | Siz əminsiniz\? Bu geri qaytarıla bilməz! 15 | Ekran yazıcı 16 | Ekran yazılır … 17 | Görüntünü saxla 18 | Dayan 19 | Səs yoxdur 20 | Paylaş 21 | 22 | Səs 23 | Mikrofon 24 | Video 25 | Səs yazılır … 26 | Aktiv qeyd 27 | Fasilə ver 28 | Davam etdir 29 | Yazma bitdi 30 | Tərcümə 31 | Hamısın sil 32 | Yaz 33 | Səsləndir 34 | Geri 35 | Tənzimləmələr 36 | Seçimlər 37 | Nümunə sürəti 38 | Bit sürəti 39 | Avtomatik 40 | Haqqında 41 | Mənbə kodu 42 | Müəllif 43 | Standart 44 | Videokamera 45 | İşlənməmiş 46 | Mono 47 | Stereo 48 | Tema 49 | Sistem 50 | İşıqlı 51 | Qaranlıq 52 | İtkisiz səs yazıcı (WAV) 53 | Qeyd, digər səsə səciyyəvi seçimlər istifadə vaxtı tətbiq edilməyəcək. 54 | -------------------------------------------------------------------------------- /app/src/main/res/values-be/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | ОК 4 | Адмена 5 | Імя файла 6 | Пуста. 7 | Запісаць гук 8 | Запіс экрана 9 | Запіс экрана … 10 | Аўдыё 11 | Відэа 12 | Паўза 13 | Каталог 14 | Выберыце каталог 15 | Фармат аўдыё 16 | Без аўдыё 17 | Частата дыскрэтызацыі 18 | Бітрэйт 19 | Па змаўчанні 20 | Светлая 21 | Запісы 22 | Перайменаваць 23 | Выдаліць 24 | Як у сістэме 25 | Цёмная 26 | Запіс завершаны 27 | Налады 28 | Параметры 29 | Мона 30 | Стэрэа 31 | Пераклад 32 | Мікрафон 33 | Аўто 34 | Аб праграме 35 | Зыходны код 36 | Аўтар 37 | Выдаліць усе 38 | Тэма 39 | Вы ўпэўнены? Гэтае дзеянне немагчыма адмяніць! 40 | Аднавіць 41 | Запіс экрана 42 | Запіс аўдыя… 43 | Стоп 44 | Падзяліцца 45 | Адкрыць 46 | Актыўны запіс 47 | Прайграць 48 | Запіс 49 | Назад 50 | Відэакамера 51 | Неапрацаваны 52 | Аўдыязапіс без страт (WAV) 53 | Звярніце ўвагу, што іншыя параметры гуку не будуць прымяняцца падчас выкарыстання. 54 | Парадак сартавання 55 | Алфавітны 56 | Алфавітны (перавернуты) 57 | Памер 58 | Памер (перавернуты) 59 | Шаблон наймення 60 | %d: дата, %t: час, %s: секунды эпохі, %m: эпоха ў мілісекундах 61 | Чорная 62 | Анатацыя запісу экрана 63 | Паказаць інструмент анатацый падчас запісу экрана 64 | Выберыце дыяпазон абрэзкі 65 | Рэжым малявання 66 | Абразанне… 67 | Абрэзаць 68 | Пачатак: 69 | Выбраная папка недаступная! 70 | Пазнакі часу аўдыявізуалізатара 71 | Паказаць пазнакі часу на часовай шкале аўдыявізуалізатара. 72 | Канец: 73 | Не ўстаноўлены 74 | Абрэзка прайшла паспяхова 75 | Пачаць абрэзку 76 | Па змаўчанні 77 | Рэжым сцірання 78 | Не ўдалося абрэзаць 79 | -------------------------------------------------------------------------------- /app/src/main/res/values-bn/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | রেকর্ডগুলো 4 | বাতিল 5 | পুণঃনামকরণ 6 | ফাইলের নাম 7 | মুছুন 8 | শব্দ রেকর্ড করুন 9 | ডিরেক্টরি নির্বাচন করুন 10 | অডিও ফর্ম্যাট 11 | আচ্ছা 12 | কিছুই নেই 13 | আপনি নিশ্চিত\? এটি পূর্বাবস্থায় ফেরত নেয়া সম্ভব হবে না! 14 | শেয়ার 15 | স্ক্রিন রেকর্ডার 16 | স্ক্রিন রেকর্ড করা হচ্ছে … 17 | স্ক্রিনরেকর্ড করুন 18 | থামুন 19 | মাইক্রোফোন 20 | শব্দহীন 21 | খুলুন 22 | শব্দ 23 | ডাইরেক্টরি 24 | ভিডিও 25 | -------------------------------------------------------------------------------- /app/src/main/res/values-ca/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Enregistraments 4 | D\'acord 5 | Atura 6 | Per defecte 7 | Videocàmera 8 | No processat 9 | Mono 10 | Mostra l\'eina d\'anotació durant la gravació de la pantalla 11 | Negre 12 | Retallat correctament 13 | Cancel·la 14 | Canvia el nom 15 | Nom del fitxer 16 | Suprimeix 17 | Esteu segur? No es pot desfer. 18 | No hi ha res aquí. 19 | Grava so 20 | Gravadora de pantalla 21 | Gravant la pantalla… 22 | Grava la pantalla 23 | Enregistrant àudio… 24 | Comparteix 25 | Obre 26 | Àudio 27 | Vídeo 28 | Enregistrament actiu 29 | Pausa 30 | Reprèn 31 | Enregistrament finalitzat 32 | Enregistra 33 | Reprodueix 34 | Enrere 35 | Ordena 36 | Alfabètic 37 | Alfabètic (invers) 38 | Mida 39 | Mida (inversa) 40 | Configuració 41 | Opcions 42 | Directori 43 | Tria el directori 44 | Format d\'àudio 45 | Micròfon 46 | Sense àudio 47 | Freqüència de mostreig 48 | Taxa de bits 49 | Auto 50 | Estèreo 51 | Gravació d\'àudio sense pèrdues (WAV) 52 | Tingues en compte que no s\'aplicaran altres opcions específiques d\'àudio. 53 | Patró de nomenclatura 54 | %d: Data, %t: Hora, %s: Segons de l\'època, %m: Mil·lisegons de l\'època 55 | Quant a 56 | Codi font 57 | Autor 58 | Traducció 59 | Suprimeix-ho tot 60 | Tema 61 | Sistema 62 | Fosc 63 | Clar 64 | Anotació de la gravadora de pantalla 65 | Per defecte 66 | Marques de temps del visualitzador d\'àudio 67 | Mostra les marques de temps a la línia de temps del visualitzador d\'àudio. 68 | Retalla 69 | Comença a retallar 70 | Atura: 71 | No definit 72 | Inici: 73 | Selecciona l\'interval de retall 74 | No s\'ha pogut retallar 75 | Retallant… 76 | No es pot accedir a la carpeta seleccionada. 77 | Mode de dibuix 78 | Mode d\'esborrar 79 | -------------------------------------------------------------------------------- /app/src/main/res/values-fa/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | ضبط 4 | میکروفون 5 | اندازه 6 | موارد ضبط‌شده 7 | بی‌صدا 8 | فرمت صدا 9 | %d: تاریخ، %t: زمان، %s: ثانیه، %m: میلی‌ثانیه 10 | توجه داشته باشید که دیگر گزینه‌های ویژه صدا درهنگام استفاده اعمال نمی‌شوند. 11 | مونو 12 | درحال ضبط صدا … 13 | ضبط صدا 14 | الگوی نام‌گذاری 15 | درنگ 16 | چیزی اینجا نیست. 17 | تغییر نام 18 | پوشه 19 | انتخاب ناحیه برش 20 | حالت نگارگری 21 | درحال برش… 22 | پوسته 23 | ضبط‌کننده صفحه 24 | بر اساس الفبا 25 | از سرگیری 26 | کد منبع 27 | روشن 28 | نمایش ابزار حاشیه‌نویسی هنگام ضبط صفحه 29 | بازگشت 30 | حذف 31 | استریو 32 | برش 33 | توقف 34 | آغاز: 35 | انتخاب پوشه 36 | دسترسی به پوشه انتخاب‌شده امکان‌پذیر نیست! 37 | بر اساس الفبا (وارونه) 38 | مهرهای زمانی تصویرساز صوتی 39 | درباره ما 40 | گزینه‌ها 41 | اندازه (وارونه) 42 | ترجمه 43 | نمایش مهرهای زمانی در جدول زمانی تصویرساز صوتی. 44 | توقف: 45 | تیره 46 | نام پرونده 47 | لغو 48 | بازکردن 49 | آیا مطمئن هستید؟ این کار برگشت‌ناپذیر است! 50 | تنظیم نشده 51 | نرخ بیت 52 | درحال ضبط صفحه … 53 | باشد 54 | پیش‌فرض 55 | خودکار 56 | چینش 57 | ویدیو 58 | پاک کردن همه 59 | ضبط به پایان رسید 60 | تنظیمات 61 | ضبط صفحه 62 | نرخ نمونه 63 | پخش 64 | برش موفق بود 65 | پردازش‌نشده 66 | نویسنده 67 | دوربین فیلم‌برداری 68 | مشکی 69 | آغاز برش 70 | سامانه 71 | پیش‌فرض 72 | هم‌رسانی 73 | حالت پاک‌کردن 74 | برش شکست خورد 75 | حاشیه‌نویسی ضبط صفحه 76 | ضبط فعال 77 | صدا 78 | ضبط صدا بدون افت (WAV) 79 | اصلاح شده 80 | اصلاح شده (معکوس) 81 | -------------------------------------------------------------------------------- /app/src/main/res/values-fil/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Mga Rekord 4 | Sige 5 | Kanselahin 6 | Palitan ang pangalan 7 | Pangalan ng file 8 | Burahin 9 | Sigurado ka ba\? Wala nang bawian! 10 | Walang nandito 11 | Magrekord ng tunog 12 | Pangrekord ng screen 13 | Nirerekord ang screen … 14 | Irekord ang screen 15 | Nagrerekord ng tunog … 16 | Hinto 17 | Ibahagi 18 | Buksan 19 | Tunog 20 | Video 21 | Aktibong pagrerekord 22 | I-pause 23 | Ituloy 24 | Natapos ang pagrerekord 25 | Rekord 26 | I-play 27 | Bumalik 28 | Mga Setting 29 | Mga Opsyon 30 | Directory 31 | Pumili ng directory 32 | Format ng tunog 33 | Mikropono 34 | Walang tunog 35 | Sample rate 36 | Bitrate 37 | Awto 38 | Default 39 | Camcorder 40 | \'Di-pinroseso 41 | Mono 42 | Stereo 43 | Tungkol 44 | Source code 45 | May-akda 46 | Pagsasalin 47 | Burahin lahat 48 | Tema 49 | Sistema 50 | Madilim 51 | Maliwanag 52 | -------------------------------------------------------------------------------- /app/src/main/res/values-iw/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | מחיקה 4 | ביטול 5 | אין כאן כלום. 6 | עצירה 7 | בחירת תיקייה 8 | קצב דגימה 9 | הקלטות 10 | מחיקה של הכול 11 | ערכת עיצוב 12 | בהירה 13 | אישור 14 | שינוי שם 15 | שם קובץ 16 | להמשיך? זה בלתי הפיך! 17 | הקלטת שמע 18 | פתיחה 19 | מקליט מסך 20 | המסך מוקלט… 21 | הקלטת המסך 22 | הקלטה פעילה 23 | השמע מוקלט… 24 | שמע 25 | שיתוף 26 | וידאו 27 | השהיה 28 | המשך 29 | נגינה 30 | ההקלטה הסתיימה 31 | הקלטה 32 | חזרה 33 | הגדרות 34 | אפשרויות 35 | תיקייה 36 | קצב סיביות 37 | תצורת שמע 38 | מיקרופון 39 | אין שמע 40 | מונו 41 | אוטומטי 42 | ברירת מחדל 43 | על היישומון 44 | מצלמת וידאו 45 | ללא עיבוד 46 | סטריאו 47 | מקליט שמע ללא אובדן איכות (WAV) 48 | נא לשים לב שהגדרות ייעודיות לשמע לא תחולנה במהלך השימוש. 49 | כהה 50 | קוד מקור 51 | יוצר 52 | תרגום 53 | מערכת 54 | גודל 55 | %d: תאריך, %t: שעה, %s: שניות זמן יוניקס, %m: מילישניות זמן יוניקס 56 | מיון 57 | אלפביתי (הפוך) 58 | אלפביתי 59 | תבנית מתן שמות 60 | גודל (הפוך) 61 | סימון על הקלטת מסך 62 | הצגת כלי סימון במהלך הקלטת מסך 63 | שחור 64 | ברירת מחדל 65 | בחירת טווח חיתוך 66 | מתבצע חיתוך… 67 | חיתוך 68 | התחלה: 69 | אי אפשר לגשת לתיקייה הנבחרת! 70 | חותמות ממחיש שמע 71 | הצגת חותמות זמן בציר הזמן של ממחיש השמע. 72 | עצירה: 73 | לא הוגדר 74 | החיתוך הצליח 75 | התחלת חיתוך 76 | החיתוך נכשל 77 | -------------------------------------------------------------------------------- /app/src/main/res/values-ja/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 録音 4 | OK 5 | キャンセル 6 | 名前を変更 7 | ファイル名 8 | 削除 9 | 本当に削除しますか?これは取り消せません! 10 | ここには何もありません 11 | 12 | 音声を録音 13 | スクリーンレコーダー 14 | 録画画面… 15 | 画面録画 16 | 音声を録音しています… 17 | ストップ 18 | 共有 19 | 開く 20 | 音声 21 | ビデオ 22 | アクティブな録音 23 | 一時停止 24 | 履歴 25 | 録音が終了しました 26 | 録音 27 | 再生 28 | 戻る 29 | 30 | ソート 31 | アルファベット 32 | アルファベット(降順) 33 | サイズ 34 | サイズ(降順) 35 | 36 | 設定 37 | オプション 38 | ディレクトリ 39 | ディレクトリを選択 40 | 音声フォーマット 41 | マイク 42 | 音声なし 43 | サンプルレート 44 | ビットレート 45 | 自動 46 | デフォルト 47 | ビデオカメラ 48 | 未加工 49 | モノラル 50 | ステレオ 51 | ロスレスオーディオレコーダー(WAV) 52 | 注意 他のオーディオ固有のオプションは、使用中は適用されません。 53 | 命名パターン 54 | %d: 日付、 %t: 時間、 %s: エポック秒、 %m: エポックミリ秒 55 | 56 | 詳細 57 | ソースコード 58 | 作者 59 | 翻訳 60 | 全て削除 61 | 62 | テーマ 63 | システム 64 | ダーク 65 | ライト 66 | 画面録画中の注釈 67 | 画面録画中に注釈ツールを表示します 68 | Amoledダーク 69 | 70 | デフォルト 71 | オーディオビジュアライザーのタイムスタンプ 72 | オーディオビジュアライザーのタイムラインにタイムスタンプを表示します。 73 | トリム 74 | トリミングを開始する 75 | 終了: 76 | 設定されていません 77 | 開始: 78 | トリムする範囲を選択してください 79 | トリムに失敗しました 80 | トリミングしています… 81 | トリムに成功しました 82 | 選択したフォルダーにアクセス出来ません! 83 | 84 | -------------------------------------------------------------------------------- /app/src/main/res/values-ms/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Nama semula 4 | Adakah anda pasti\? Ini tidak boleh dibuat semula! 5 | Tiada di sini 6 | Rakaman aktif 7 | Nama fail 8 | Rakam bunyi 9 | Skrin dirakam … 10 | Perakam skrin 11 | Direktori 12 | Rakaman selesai 13 | Rakam skrin 14 | Merakam audio … 15 | Berhenti 16 | Kongsi 17 | Buka 18 | Audio 19 | Video 20 | Berhenti 21 | Menyambung semula 22 | Rakam 23 | Main 24 | Kembali 25 | Pilihan 26 | Tetapan 27 | Format audio 28 | Kadar bit 29 | Mikrofon 30 | Tiada audio 31 | Kadar sampel 32 | Automatik 33 | Lalai 34 | Tidak diproses 35 | Mono 36 | Stereo 37 | Kamera video 38 | Terjemahan 39 | Padam semua 40 | Gelap 41 | Sistem 42 | Padam 43 | Tema 44 | Cahaya 45 | Perihal 46 | Kod sumber 47 | Pengarang 48 | Pilih direktori 49 | Rakaman-rakaman 50 | Baik 51 | Batal 52 | -------------------------------------------------------------------------------- /app/src/main/res/values-my/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | အိုကေ 4 | မလုပ်တော့ 5 | ဖွင့်မည် 6 | အသံ 7 | ဗွီဒီယို 8 | လက်ရှိဖမ်းယူနေသည် 9 | ခဏရပ် 10 | ပြန်စ 11 | စီမည် 12 | အက္ခရာစဥ်လိုက် 13 | အက္ခရာစဥ်လိုက် (ပြောင်းပြန်) 14 | မိုက်ခရိုဖုန်း 15 | အသံမရှိ 16 | %d: ရက်စွဲ, %t: အချိန်, %s: စက္ကန့်, %m: မီလီ 17 | အကြောင်း 18 | ရင်းမြစ်ကုဒ် 19 | ဖန်တီးသူ 20 | ဘာသာပြန် 21 | အားလုံးဖျက်မည် 22 | သင်း 23 | ဖြတ်ထုတ် 24 | ရပ်: 25 | ဖမ်းယူမှုများ 26 | အမည်ပြောင်းမည် 27 | ဖိုင်အမည် 28 | ဖျက်မည် 29 | သင်သေချာပြီလား?ပြန်လည်ပြင်ဆင်၍မရနိုင်ပါ။ 30 | ဘာမှမရှိပါ။ 31 | အသံဖမ်း 32 | စခရင်မှတ်တမ်း 33 | စခရင်မှတ်တမ်းတင်နေသည်… 34 | စခရင်မှတ်တမ်းတင်ရန် 35 | အသံဖမ်းယူနေသည်… 36 | ရပ်မည် 37 | ဆက်တင်များ 38 | ဖမ်းယူမှုပြီးပြီ 39 | နောက် 40 | ဖမ်းယူ 41 | မျှဝေမည် 42 | ကြည့်မည် 43 | ရွေးစရာများ 44 | ဖိုင်လမ်းကြောင်း 45 | ဆိုဒ် 46 | ဆိုဒ် (ပြောင်းပြန်) 47 | ဖိုင်လမ်းကြောင်းရွေးပါ 48 | အသံဖိုင်ပုံစံ 49 | ရိုးရိုးကြိမ်နှုန်း 50 | နှစ်ဆကြိမ်နှုန်း 51 | အလိုအလျောက် 52 | ပုံသေ 53 | စခရင်ဦးစားပေး 54 | ပုံသေဖြစ်ရိုး 55 | မိုနို 56 | မပြုပြင်ရသေး 57 | လို့လက်စ် အသံဖမ်းယူမှု (ဒဗလျှူအေဗွီ) 58 | အမည်ပေးအထာ 59 | အခြားသောအသံဆိုင်ရာဆက်တင်သည်အသုံးပြုနေစဥ်မပြောင်းနိုင်ပါ။ 60 | စနစ် 61 | အမှောင် 62 | အလင်း 63 | အနက် 64 | ပုံသေ 65 | စခရင် ဖမ်းယူမှုအကူကိရိယာများ 66 | အသံပြုပြင်မှုတွင်အသံဖြတ်များကိုပြပါ။ 67 | စခရင် ဖမ်းယူစဥ် အကူကိရိယာများကိုပြပါ 68 | အသံဖြတ်များ 69 | ဖြတ်ထုတ်မှုအကွာအဝေးရွေးချယ်ပါ 70 | ဖြတ်ထုတ်မည် 71 | မသတ်မှတ် 72 | ဖြတ်ထုတ်မှုမအောင်မြင် 73 | စ: 74 | ဖြတ်ထုတ်နေသည်… 75 | ဖြတ်ထုတ်မှုအောင်မြင် 76 | ဖိုလ်ဒါမရွေးနိုင်! 77 | ပုံဆွဲမုဒ် 78 | ခဲဖျက်မုဒ် 79 | -------------------------------------------------------------------------------- /app/src/main/res/values-nb-rNO/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Format 4 | Gi nytt navn 5 | Slett 6 | Filnavn 7 | Opptak 8 | Ingenting her 9 | Ta opp lyd 10 | Mappe 11 | Velg katalog 12 | Utfør uten mulighet for angring\? 13 | OK 14 | Avbryt 15 | Opptak fullført 16 | Spill 17 | Tilbake 18 | Ingen lyd 19 | Samplingstakt 20 | Bitrate 21 | Automatisk 22 | Tar opp lyd … 23 | Del 24 | Åpne 25 | Lyd 26 | Ta opp 27 | Kildekode 28 | Skjermopptaker 29 | Spiller inn skjermaktivitet … 30 | Ta opp skjermen 31 | Mikrofon 32 | Stopp 33 | Video 34 | Tapsfri lydopptaker (WAV) 35 | Merk at andre lydspesifikke alternativer ikke har innvirkning ved bruk. 36 | Mørk 37 | Lys 38 | System 39 | Innstillinger 40 | Alternativer 41 | Aktivt opptak 42 | Pause 43 | Fortsett 44 | Forvalg 45 | Videokamera 46 | Ubehandlet 47 | Mono 48 | Stereo 49 | Om 50 | Skaper 51 | Oversettelse 52 | Slett alt 53 | Drakt 54 | Størrelse 55 | %d: dato, %t: tid, %s: sekunder, %m: millisekunder 56 | Navnemønster 57 | Velg beskjæringsområde 58 | Beskjærer … 59 | A–Å 60 | Vis undertekstsverktøy under skjermopptak 61 | Beskjær 62 | Start: 63 | Har ikke tilgang til valgt mappe. 64 | Å–A 65 | Lydvisualiseringstidsstempel 66 | Størrelse (omvendt) 67 | Vis tidsstempler i lydvisualiseringstidslinjen. 68 | Stopp: 69 | Ikke satt 70 | Sortering 71 | Beskjært 72 | Nattsvart 73 | Start beskjæring 74 | Forvalg 75 | Kunne ikke utføre beskjæring 76 | Skjermopptaksundertekst 77 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FF000000 4 | #FFFAB387 5 | -------------------------------------------------------------------------------- /app/src/main/res/values-nn/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Avbryt 4 | Opptak 5 | Greitt 6 | Nemn om 7 | Filnamn 8 | Slett 9 | Ingenting her 10 | Er du viss\? Dette kan ikkje verta angra. 11 | Tak opp ljod 12 | Mappe 13 | Vel mappe 14 | Skjermopptak 15 | Tek opp skjermen … 16 | Tak opp skjermen 17 | Stans 18 | Mikrofon 19 | Ingen ljod 20 | Del 21 | Opne 22 | Format 23 | Ljod 24 | Video 25 | Fullgjorde opptaket 26 | Tak opp 27 | Spel av 28 | Attende 29 | Innstillingar 30 | Val 31 | Bitrate 32 | Om 33 | Tapsfri ljodopptak (WAV) 34 | Merk at andre ljodval ikkje gjeld under bruk. 35 | Kjeldekode 36 | Omsetjing 37 | Slett alt 38 | System 39 | Mørk 40 | Ljos 41 | Tek opp ljod … 42 | Hald fram 43 | Forval 44 | Videokamera 45 | Uhandsama 46 | Mono 47 | Stereo 48 | Forfattar 49 | Vising 50 | -------------------------------------------------------------------------------- /app/src/main/res/values-or/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | ସମ୍ବନ୍ଧରେ 4 | ଡିରେକ୍ଟୋରୀ ବାଛନ୍ତୁ | 5 | ସେଟିଂସମୂହ 6 | ବିରତି 7 | ବାତିଲ୍ କରନ୍ତୁ 8 | ଚଲାନ୍ତୁ 9 | -------------------------------------------------------------------------------- /app/src/main/res/values-pt/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Renomear 4 | Cancelar 5 | Formato de áudio 6 | Gravações 7 | Nome do ficheiro 8 | Escolher diretório 9 | Nada aqui. 10 | Gravar som 11 | Diretório 12 | Gravador de ecrã 13 | A gravar ecrã … 14 | Gravar ecrã 15 | Parar 16 | Abrir 17 | Áudio 18 | Partilhar 19 | Microfone 20 | Sem áudio 21 | Pausa 22 | Retomar 23 | Tem a certeza? Isto não pode ser desfeito! 24 | Eliminar 25 | Ok 26 | Vídeo 27 | Gravar áudio… 28 | Gravação ativa 29 | Acerca 30 | Tema 31 | Claro 32 | Tradução 33 | Gravação terminada 34 | Sistema 35 | Escuro 36 | Gravar 37 | Reproduzir 38 | Recuar 39 | Definições 40 | Opções 41 | Taxa de amostragem 42 | Taxa de bits 43 | Automático 44 | Código-fonte 45 | Autor 46 | Eliminar tudo 47 | Câmara de vídeo 48 | Mono 49 | Estéreo 50 | Não processado 51 | Padrão 52 | Gravador de áudio sem perdas (WAV) 53 | Atenção que as opções específicas de áudio não serão aplicadas durante a utilização. 54 | Tamanho 55 | Padrão de nomes 56 | Alfabética 57 | Ordem 58 | Alfabética (invertida) 59 | Tamanho (invertido) 60 | %d: Data, %t: Hora, %s: Segundos, %m: Minutos 61 | Escuro 62 | Anotação de gravação de ecrã 63 | Mostrar ferramenta de anotação durante a gravação de ecrã 64 | Padrão 65 | Selecione o intervalo de corte 66 | Recorte 67 | Início: 68 | Marca temporal de visualização áudio 69 | Mostrar data e hora na cronologia da visualização de áudio 70 | Parar: 71 | Não definido 72 | Iniciar recorte 73 | Falha ao recortar 74 | Alterado (invertido) 75 | A aparar… 76 | Não pode acessar a pasta selecionada! 77 | Modo de desenho 78 | Modo de apagar 79 | Alterado 80 | -------------------------------------------------------------------------------- /app/src/main/res/values-ro/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Înregistrează ecranul 4 | Format audio 5 | Înregistrări 6 | Bine 7 | Anulează 8 | Redenumire 9 | Nume de fișier 10 | Șterge 11 | Eşti sigur? Acest lucru nu poate fi anulat! 12 | Nimic aici. 13 | Înregistrare sunet 14 | Director 15 | Alegeți directorul 16 | Înregistrator de ecran 17 | Se înregistrează ecranul … 18 | Se înregistrează audio… 19 | Opreşte 20 | Fără sunet 21 | Partajați 22 | Deschide 23 | Audio 24 | Video 25 | Înregistrare activă 26 | Pauză 27 | Relua 28 | Microfon 29 | Înregistrează 30 | Redă 31 | Opțiuni 32 | Rată simplă 33 | Rată de biți 34 | Auto 35 | Despre 36 | Cod sursa 37 | Autor 38 | Traducere 39 | Șterge tot 40 | Întunecat 41 | Luminos 42 | Înregistrare terminată 43 | Înapoi 44 | Setări 45 | Temă 46 | Sistem 47 | Implicit 48 | Cameră video 49 | Neprocesat 50 | Mono 51 | Stereo 52 | Înregistrare audio fără pierderi (WAV) 53 | Rețineți că alte opțiuni specifice audio nu se vor aplica în timpul utilizării. 54 | Sortare 55 | Alfabetic 56 | Alfabetic (inversat) 57 | Dimensiune 58 | Dimensiune (inversată) 59 | Model de denumire 60 | %d: Data, %t: Ora, %s: Epoch secunde, %m: Epoch milis 61 | Adnotare înregistrare ecran 62 | Afișare instrument adnotare în timpul înregistrării ecranului 63 | Negru 64 | Implicit 65 | Modificat 66 | Modificat (inversat) 67 | -------------------------------------------------------------------------------- /app/src/main/res/values-ryu/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | るくゎらん 4 | OK 5 | キャンセル 6 | やーめーへいるかん 7 | ファイルめい 8 | さちゅるじょ 9 | ふんとうんかいさちゅるじょさびーが?くれーとぅりきしやびらん! 10 | くまんかえーぬーんあいびらん 11 | 12 | うんせいるくゎらん 13 | スクリーンレコーダー 14 | るくががみん… 15 | やしがみんるくが 16 | うんせいるくゎらんそーいびーん… 17 | ストップ 18 | ちゅーゆーいん 19 | ふぃらく 20 | うんせい 21 | ビデオ 22 | アクティブやるるくゎらん 23 | いちじていし 24 | りりき 25 | るくゎらんしがしゅうりょうさびたん 26 | るくゎらん 27 | さいせい 28 | むどぅいん 29 | 30 | ソート 31 | アルファベット 32 | アルファベット(こうじゅん) 33 | サイズ 34 | サイズ(こうじゅん) 35 | 36 | しってい 37 | オプション 38 | ディレクトリ 39 | ディレクトリしんたく 40 | うんせいフォーマット 41 | マイク 42 | うんせいなし 43 | サンプルレート 44 | ビットレート 45 | じちゃー 46 | デフォルト 47 | ビデオカメラ 48 | んーじかくいん 49 | モノラル 50 | ステレオ 51 | ロスレスオーディオレコーダー(WAV) 52 | ちゅうい ふかぬオーディオくゆうぬオプションー、さうちゅうはてぃきようさりましん。 53 | なーめーめーパターン 54 | %d: ふぃいづけい、 %t: じがん、 %s: エポックびょう、 %m: エポックミリびょう 55 | 56 | しょうさい 57 | ソースコード 58 | さくしゃ 59 | ふんやちゅん 60 | まじりさちゅるじょ 61 | 62 | テーマ 63 | システム 64 | ダーク 65 | ライト 66 | やしがみんるくがちゅうぬちゅうしゃく 67 | やしがみんるくがちゅうにちゅうしゃくちーるひょうじさびーん 68 | Amoledダーク 69 | 70 | デフォルト 71 | オーディオビジュアライザーぬタイムスタンプ 72 | オーディオビジュアライザーぬタイムラインんかいタイムスタンプひょうじさびーん。 73 | トリム 74 | トリミングかいしすん 75 | しゅうりょう: 76 | しっていさりやびらん 77 | かいし: 78 | トリムしーんはんうぅいしんたくしてぃくぃみそーれー 79 | トリムんかいしっぺーさびたん 80 | トリミングそーいびーん… 81 | トリムせいかんさびたん 82 | しんたくさるフォルダーんかいアクセスなやびらん! 83 | 84 | -------------------------------------------------------------------------------- /app/src/main/res/values-sat/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | ᱴᱷᱤᱠ 4 | ᱢᱮᱴᱟᱣ 5 | ᱟᱢ ᱡᱷᱚᱛᱚ ᱞᱮᱠᱷᱟᱛᱮ ᱠᱚᱨᱟᱣ ᱥᱟᱱᱟᱢ ᱠᱟᱱᱟ ᱥᱮ \? ᱱᱚᱶᱟ ᱫᱚ ᱨᱩᱣᱟᱹᱲ ᱟᱹᱜᱩ ᱵᱟᱭ ᱜᱟᱱᱚᱜᱼᱟ ! 6 | ᱱᱚᱰᱮ ᱫᱚ ᱪᱮᱫ ᱦᱚᱸ ᱵᱟᱹᱱᱩᱜᱼᱟ 7 | ᱨᱮᱠᱚᱰᱤᱝᱠᱚ 8 | ᱵᱟᱹᱰᱨᱟᱹ 9 | ᱫᱩᱦᱲᱟᱹ ᱧᱩᱛᱩᱢ ᱚᱞ 10 | ᱨᱮᱫᱽᱧᱩᱛᱩᱢ 11 | ᱛᱚᱨᱡᱚᱢᱟ 12 | -------------------------------------------------------------------------------- /app/src/main/res/values-sv/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Inspelningar 4 | Pausa 5 | Byt namn 6 | Alfabetisk 7 | Återuppta 8 | Ta bort 9 | Alfabetisk (omvänd) 10 | Om 11 | Filnamn 12 | Avbryt 13 | Öppna 14 | Är du säker? Detta kan inte ångras! 15 | Sortera 16 | Ta bort alla 17 | Inställningar 18 | Skapare 19 | Dela 20 | Ingenting här. 21 | Okej 22 | Spela in ljud 23 | Skärminspelning 24 | Spelar in skärm … 25 | Spela in skärm 26 | Spelar in ljud … 27 | Stopp 28 | Ljud 29 | Video 30 | Pågående inspelning 31 | Inspelning avslutad 32 | Spela in 33 | Play 34 | Bakåt 35 | Storlek 36 | Storlek (omvänd) 37 | Val 38 | Mapp 39 | Välj mapp 40 | Ljudformat 41 | Mikrofon 42 | Inget ljud 43 | Samplingsfrekvens 44 | -------------------------------------------------------------------------------- /app/src/main/res/values-zh-rCN/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 你确定吗?此操作无法撤销! 4 | 这里空无一物。 5 | 录音 6 | 选择目录 7 | 确定 8 | 音频格式 9 | 重命名 10 | 录制列表 11 | 取消 12 | 文件名 13 | 删除 14 | 目录 15 | 屏幕录制器 16 | 正在录屏 … 17 | 录屏 18 | 停止 19 | 话筒音频 20 | 无音频 21 | 分享 22 | 打开 23 | 音频 24 | 视频 25 | 录制活动中 26 | 暂停 27 | 正在录音… 28 | 恢复 29 | 设置 30 | 记录 31 | 播放 32 | 返回 33 | 源代码 34 | 翻译 35 | 删除全部 36 | 录制完毕 37 | 作者 38 | 选项 39 | 关于 40 | 自动 41 | 跟随系统 42 | 暗色 43 | 比特率 44 | 采样率 45 | 主题 46 | 浅色 47 | 单声道 48 | 摄像头音频 49 | 默认 50 | 原始音频 51 | 立体声 52 | 无损录音(WAV) 53 | 请注意,使用此选项时将不会应用其他音频设置。 54 | 排序 55 | 大小 56 | 大小(倒序) 57 | 首字母(倒序) 58 | 文件命名模式 59 | %d: 日期, %t: 时间, %s: 纪元秒, %m: 纪元毫秒 60 | 按字母表顺序 61 | 录屏注释 62 | 在录屏期间显示注释工具 63 | 黑色 64 | 默认 65 | 选择剪辑范围 66 | 剪辑中…… 67 | 剪辑 68 | 起始点: 69 | 已选的文件夹无法访问! 70 | 音频可视化时间戳 71 | 在音频可视化时间线上显示时间戳。 72 | 结束点: 73 | 未设定 74 | 剪辑成功 75 | 开始剪辑 76 | 剪辑失败 77 | 绘图模式 78 | 擦除模式 79 | 修改日期 80 | 修改日期(倒序) 81 | 内部音频(Root) 82 | 没有足够的权限开始录制 83 | -------------------------------------------------------------------------------- /app/src/main/res/values-zh-rTW/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 螢幕錄製 4 | 錄製清單 5 | 確定 6 | 取消 7 | 重新 8 | 檔名 9 | 確定刪除?刪掉後救不回喔! 10 | 這裏甚麼都沒有。 11 | 螢幕錄製 12 | 停止 13 | 音檔 14 | 影片 15 | 啟用錄音 16 | 暫停 17 | 繼續 18 | 錄音 19 | 播放 20 | 往前 21 | 設定 22 | 選項 23 | 檔案目錄 24 | 音檔格式 25 | 麥克風 26 | 無音檔 27 | 取樣率 28 | 位元率 29 | 攝影機 30 | 未加工 31 | 單聲道 32 | 雙聲道 33 | 關於 34 | 原始碼 35 | 全部刪除 36 | 主題 37 | 系統 38 | 暗黑風格 39 | 亮堂風格 40 | 開始錄製 41 | 刪除 42 | 正在錄製中… 43 | 分享 44 | 開啟 45 | 正在錄製螢幕… 46 | 選擇檔案目錄 47 | 已完成錄製 48 | 創作者 49 | 自動 50 | 預設 51 | 翻譯 52 | 排序 53 | 按字母順序 54 | 繪圖模式 55 | 擦除模式 56 | 修改日期 57 | 大小(倒序) 58 | %d: 日期, %t: 時間, %s: 紀元秒, %m: 紀元毫秒 59 | 黑色 60 | 在音訊視覺化時間線上顯示時間戳記。 61 | 剪輯失敗 62 | 剪輯中… 63 | 剪輯成功 64 | 起始點: 65 | 選擇剪輯範圍 66 | 預設 67 | 錄影畫面註釋 68 | 在錄影畫面期間顯示註釋工具 69 | 修改日期(倒序) 70 | 首字母(倒序) 71 | 大小 72 | 無損錄音(WAV) 73 | 請注意,使用此選項時將不會套用其他音訊設定。 74 | 檔案命名模式 75 | 音訊視覺化時間戳 76 | 開始剪輯 77 | 剪輯 78 | 結束點: 79 | 未設定 80 | 已選的資料夾無法存取! 81 | 內部音訊(Root) 82 | 沒有相應的權限,無法開始錄製 83 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFFFF 4 | #FFCBA6F7 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #EFF1F5 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/xml/provider_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/test/java/com/bnyro/recorder/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.bnyro.recorder 2 | 3 | import org.junit.Assert.* 4 | import org.junit.Test 5 | 6 | /** 7 | * Example local unit test, which will execute on the development machine (host). 8 | * 9 | * See [testing documentation](http://d.android.com/tools/testing). 10 | */ 11 | class ExampleUnitTest { 12 | @Test 13 | fun addition_isCorrect() { 14 | assertEquals(4, 2 + 2) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | buildscript { 2 | }// Top-level build file where you can add configuration options common to all sub-projects/modules. 3 | plugins { 4 | id("com.android.application") version "8.2.2" apply false 5 | id("com.android.library") version "8.2.2" apply false 6 | id("org.jetbrains.kotlin.android") version "1.9.22" apply false 7 | } -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/16.txt: -------------------------------------------------------------------------------- 1 | Important: The signing keys for this release have changed, hence you must reinstall the app and can't update it directly. 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/full_description.txt: -------------------------------------------------------------------------------- 1 | Record You is a voice and screen recorder app built with Material Design 3 (You). 2 | 3 | It supports multiple audio formats and codecs, such as M4A, AAC and OPUS. 4 | 5 | Record You uses SAF (Storage Access Framework), thus it doesn't require any storage permissons. 6 | 7 | It doesn't have any dependencies apart from the Android base and Material design libraries, therefore the app is very lightweight. 8 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/you-apps/RecordYou/2b44e4688f29b0b6d533c3012b26283e0789c10d/fastlane/metadata/android/en-US/images/icon.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/1-audio-recorder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/you-apps/RecordYou/2b44e4688f29b0b6d533c3012b26283e0789c10d/fastlane/metadata/android/en-US/images/phoneScreenshots/1-audio-recorder.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/2-screen-recorder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/you-apps/RecordYou/2b44e4688f29b0b6d533c3012b26283e0789c10d/fastlane/metadata/android/en-US/images/phoneScreenshots/2-screen-recorder.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/3-recordings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/you-apps/RecordYou/2b44e4688f29b0b6d533c3012b26283e0789c10d/fastlane/metadata/android/en-US/images/phoneScreenshots/3-recordings.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/4-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/you-apps/RecordYou/2b44e4688f29b0b6d533c3012b26283e0789c10d/fastlane/metadata/android/en-US/images/phoneScreenshots/4-settings.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/short_description.txt: -------------------------------------------------------------------------------- 1 | Privacy focused voice and screen recorder app build with MD3 2 | -------------------------------------------------------------------------------- /ghbadge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/you-apps/RecordYou/2b44e4688f29b0b6d533c3012b26283e0789c10d/ghbadge.png -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true 24 | android.nonFinalResIds=false -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/you-apps/RecordYou/2b44e4688f29b0b6d533c3012b26283e0789c10d/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Feb 18 16:19:16 IST 2024 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | gradlePluginPortal() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | } 14 | } 15 | rootProject.name = "Record You" 16 | include(":app") 17 | --------------------------------------------------------------------------------