Skip to content
This repository was archived by the owner on Dec 4, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions .github/workflows/generate-changelog.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
name: Generate Release Changelog

on:
release:
types: [created]
workflow_dispatch:
inputs:
tag:
description: 'Tag to generate changelog for'
required: true
type: string

jobs:
generate-changelog:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write

steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Get Previous Release Tag
id: get_previous_tag
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
CURRENT_TAG="${{ github.event.inputs.tag }}"
else
CURRENT_TAG="${{ github.event.release.tag_name }}"
fi

PREVIOUS_TAG=$(git tag --sort=-version:refname | grep -A1 "^${CURRENT_TAG}$" | tail -n1)

if [ "$PREVIOUS_TAG" = "$CURRENT_TAG" ]; then
# If no previous tag found, use first commit
PREVIOUS_TAG=$(git rev-list --max-parents=0 HEAD)
fi

echo "current_tag=$CURRENT_TAG" >> $GITHUB_OUTPUT
echo "previous_tag=$PREVIOUS_TAG" >> $GITHUB_OUTPUT
echo "Current: $CURRENT_TAG, Previous: $PREVIOUS_TAG"

- name: Generate Changelog from Commits
id: generate_changelog
run: |
cat > changelog_section.md << 'EOF'
## [${{ steps.get_previous_tag.outputs.current_tag }}] - $(date +%Y-%m-%d)

### Added
EOF

# Get commits between tags and categorize them
git log ${{ steps.get_previous_tag.outputs.previous_tag }}..${{ steps.get_previous_tag.outputs.current_tag }} \
--pretty=format:"- %s" \
--grep="feat:" --grep="add:" | head -20 >> changelog_section.md

echo "" >> changelog_section.md
echo "### Changed" >> changelog_section.md

git log ${{ steps.get_previous_tag.outputs.previous_tag }}..${{ steps.get_previous_tag.outputs.current_tag }} \
--pretty=format:"- %s" \
--grep="update:" --grep="change:" --grep="improve:" | head -20 >> changelog_section.md

echo "" >> changelog_section.md
echo "### Fixed" >> changelog_section.md

git log ${{ steps.get_previous_tag.outputs.previous_tag }}..${{ steps.get_previous_tag.outputs.current_tag }} \
--pretty=format:"- %s" \
--grep="fix:" --grep="bug:" | head -20 >> changelog_section.md

echo "" >> changelog_section.md

- name: Update CHANGELOG.md
run: |
if [ ! -f CHANGELOG.md ]; then
echo "# Changelog" > CHANGELOG.md
echo "" >> CHANGELOG.md
echo "All notable changes to this project will be documented in this file." >> CHANGELOG.md
echo "" >> CHANGELOG.md
fi

# Create temp file with new entry
cp CHANGELOG.md CHANGELOG_TMP.md

# Insert new changelog section after the header
awk '
/^# Changelog/ { print; getline; print; print ""; while ((getline line < "changelog_section.md") > 0) print line; close("changelog_section.md"); print "" }
!/^# Changelog/ { print }
' CHANGELOG.md > CHANGELOG_NEW.md

mv CHANGELOG_NEW.md CHANGELOG.md
rm -f changelog_section.md

- name: Check for Changes
id: check_changes
run: |
if git diff --quiet CHANGELOG.md; then
echo "has_changes=false" >> $GITHUB_OUTPUT
else
echo "has_changes=true" >> $GITHUB_OUTPUT
fi

- name: Create Pull Request
if: steps.check_changes.outputs.has_changes == 'true'
uses: peter-evans/create-pull-request@v5
with:
token: ${{ secrets.GITHUB_TOKEN }}
base: main
branch: "changelog/release-${{ steps.get_previous_tag.outputs.current_tag }}"
commit-message: "docs: update changelog for ${{ steps.get_previous_tag.outputs.current_tag }}"
title: "📜 Update Changelog for ${{ steps.get_previous_tag.outputs.current_tag }}"
body: |
**Description**:
This PR updates the CHANGELOG.md with release notes for ${{ steps.get_previous_tag.outputs.current_tag }}.

**Changes**:
- Generated changelog from commits between ${{ steps.get_previous_tag.outputs.previous_tag }} and ${{ steps.get_previous_tag.outputs.current_tag }}
- Categorized changes into Added, Changed, and Fixed sections

**Note**: Please review and edit the changelog as needed before merging.
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Changelog

All notable changes to this project will be documented in this file.

## [Unreleased]

### Added
- **Variable Speed Playback**: Audio notes can now be played at 1.0x, 1.5x, or 2.0x speed for faster review
- Playback speed preferences are saved and restored between app sessions
- Visual speed indicator in the audio player

## [1.1.4] - 2025-07-20

### Added
- Initial fork of NotelyVoice with personalisation focus
- Enhanced README with fork management and contribution guidelines
- Streamlined UI components and navigation

### Changed
- Package structure optimization for Android focus
- Updated dependencies to latest stable versions

### Fixed
- Minor UI inconsistencies in note list view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ Download the latest APK files directly from [GitHub Releases](https://github.com
🌐 **Offline Capability** - Speech recognition works without an internet connection
🔄 **Seamless Integration** - Dictate directly into notes or transcribe audio recordings
🎧 **Audio Recording** - Record voice notes and play them back within the app
🎧 **Unlimited Transcriptions** - Transcribe unlimited voice notes to multiple languages
🎧 **Unlimited Transcriptions** - Transcribe unlimited voice notes to multiple languages
⚡ **Variable Speed Playback** - Adjust audio playback speed (1x, 1.5x, 2x) for faster note review

### General
🌓 **Theming** - Switch between dark and light themes based on your preference
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
---
id: task-002
title: Implement variable speed playback for audio notes
status: To Do
assignee: []
status: In Progress
assignee:
- '@stanvx'
created_date: '2025-07-19'
updated_date: '2025-07-19'
labels: []
dependencies: []
---
Expand All @@ -14,9 +16,13 @@ Allow users to adjust playback speed (1x, 1.5x, 2x) to improve listening experie

## Acceptance Criteria

- [ ] PlatformAudioPlayer interface includes setPlaybackSpeed method
- [ ] Android implementation uses MediaPlayer.setPlaybackParams
- [ ] iOS implementation uses AVPlayer.rate
- [ ] UI provides speed selection controls (1.0x 1.5x 2.0x)
- [ ] Speed preference persists across app sessions
- [ ] Current speed is visually indicated to user
- [x] PlatformAudioPlayer interface includes setPlaybackSpeed method
- [x] Android implementation uses MediaPlayer.setPlaybackParams
- [x] iOS implementation uses AVPlayer.rate
- [x] UI provides speed selection controls (1.0x 1.5x 2.0x)
- [x] Speed preference persists across app sessions
- [x] Current speed is visually indicated to user

## Implementation Plan

1. Add setPlaybackSpeed method to PlatformAudioPlayer interface\n2. Implement Android playback speed using MediaPlayer.setPlaybackParams\n3. Implement iOS playback speed using AVPlayer.rate\n4. Add playback speed to UI and presentation state models\n5. Create speed toggle button UI component (1.0x → 1.5x → 2.0x → cycle)\n6. Add speed preference persistence using DataStore\n7. Update UI to display current speed and handle speed changes\n8. Test implementation on both platforms
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
id: task-013
title: Add quick record shortcut for streamlined audio capture
status: To Do
assignee: []
created_date: '2025-07-19'
updated_date: '2025-07-19'
labels: []
dependencies: []
priority: high
---

## Description

Implement a direct recording flow that bypasses the current multi-step process (plus → microphone → record → stop → transcribe → append → back). Users should be able to start recording with one click, stop with another click, and have transcription/append happen automatically in the background.

## Acceptance Criteria

- [ ] Quick record button available on main screen
- [ ] Single click starts recording and navigates to recording screen
- [ ] Single click stops recording and returns to main screen
- [ ] Transcription happens automatically in background after recording stops
- [ ] New note is created and transcription is appended automatically
- [ ] No manual transcription or append button interaction required
- [ ] Reuses existing recording and transcription components
- [ ] Flow reduces current 7+ clicks to just 2 clicks
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
id: task-014
title: Add support for 16KB page sizes on Android
status: To Do
assignee: []
created_date: '2025-07-19'
labels: []
dependencies: []
---

## Description

Implement support for Android devices with 16KB page sizes to ensure compatibility with newer Android devices and future-proof the application. This involves updating native code, build configurations, and testing on devices with different page sizes.

## Acceptance Criteria

- [ ] App builds and runs correctly on devices with 16KB page sizes
- [ ] Native audio processing (Whisper C++) works with 16KB pages
- [ ] SQLDelight database operations handle 16KB page sizes
- [ ] No crashes or performance degradation on 16KB page size devices
- [ ] Build configuration updated to support multiple page sizes
- [ ] Testing performed on both 4KB and 16KB page size devices
- [ ] Documentation updated with page size compatibility notes
7 changes: 6 additions & 1 deletion shared/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,18 @@ kotlin {
val commonTest by getting {
dependencies {
implementation(kotlin("test"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2")
implementation(libs.koin.test)
implementation(libs.datastore.preferences)
}
}
}

targets.all {
compilations.all {
kotlinOptions.freeCompilerArgs += "-Xexpect-actual-classes"
compilerOptions.configure {
freeCompilerArgs.add("-Xexpect-actual-classes")
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package com.module.notelycompose.platform

import android.content.Context
import android.os.Build
import android.os.VibrationEffect
import android.os.Vibrator
import android.os.VibratorManager
import android.view.HapticFeedbackConstants

actual class HapticFeedback {
private var context: Context? = null

fun initialize(context: Context) {
this.context = context
}

actual fun light() {
performHapticFeedback(HapticFeedbackType.LIGHT)
}

actual fun medium() {
performHapticFeedback(HapticFeedbackType.MEDIUM)
}

actual fun heavy() {
performHapticFeedback(HapticFeedbackType.HEAVY)
}

actual fun success() {
performHapticFeedback(HapticFeedbackType.SUCCESS)
}

actual fun error() {
performHapticFeedback(HapticFeedbackType.ERROR)
}

private fun performHapticFeedback(type: HapticFeedbackType) {
val context = this.context ?: return

try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// Android 12+ - Use VibratorManager
val vibratorManager = context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as? VibratorManager
val vibrator = vibratorManager?.defaultVibrator
vibrator?.let { performModernHapticFeedback(it, type) }
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Android 8.0+ - Use VibrationEffect
val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as? Vibrator
vibrator?.let { performModernHapticFeedback(it, type) }
} else {
// Legacy Android - Use simple vibration
val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as? Vibrator
vibrator?.let { performLegacyHapticFeedback(it, type) }
}
} catch (e: Exception) {
android.util.Log.w("HapticFeedback", "Failed to perform haptic feedback: ${e.message}")
}
}

private fun performModernHapticFeedback(vibrator: Vibrator, type: HapticFeedbackType) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val effect = when (type) {
HapticFeedbackType.LIGHT -> VibrationEffect.createOneShot(50, 50)
HapticFeedbackType.MEDIUM -> VibrationEffect.createOneShot(100, 100)
HapticFeedbackType.HEAVY -> VibrationEffect.createOneShot(150, 200)
HapticFeedbackType.SUCCESS -> VibrationEffect.createWaveform(longArrayOf(0, 50, 50, 100), -1)
HapticFeedbackType.ERROR -> VibrationEffect.createWaveform(longArrayOf(0, 100, 50, 100, 50, 100), -1)
}

if (vibrator.hasVibrator()) {
vibrator.vibrate(effect)
}
}
}

private fun performLegacyHapticFeedback(vibrator: Vibrator, type: HapticFeedbackType) {
if (vibrator.hasVibrator()) {
val duration = when (type) {
HapticFeedbackType.LIGHT -> 50L
HapticFeedbackType.MEDIUM -> 100L
HapticFeedbackType.HEAVY -> 150L
HapticFeedbackType.SUCCESS -> 200L
HapticFeedbackType.ERROR -> 300L
}

@Suppress("DEPRECATION")
vibrator.vibrate(duration)
}
}

private enum class HapticFeedbackType {
LIGHT, MEDIUM, HEAVY, SUCCESS, ERROR
}
}
Loading
Loading