├── .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 |
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 |
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/migrations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
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 |

10 |

11 |

12 |

13 |

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 | [

](https://f-droid.org/packages/com.bnyro.recorder/)
37 | [

](https://github.com/you-apps/RecordYou/releases)
38 |
39 |
40 |
41 |
42 | ## Screenshots
43 |
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 |
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 |

13 |

14 |

15 |

16 |

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 | [

](https://f-droid.org/packages/com.bnyro.recorder/)
40 | [

](https://github.com/you-apps/RecordYou/releases)
41 |
42 |
43 |
44 |
45 | ## Screenshots
46 |
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 |
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 | Aç
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 |
--------------------------------------------------------------------------------