tbr-deal-finder 0.3.1__tar.gz → 0.3.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/.github/workflows/build-release-dmg.yml +52 -20
  2. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/.github/workflows/build-release-exe.yml +3 -3
  3. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/CHANGELOG.md +23 -0
  4. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/Makefile +1 -1
  5. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/PKG-INFO +1 -1
  6. tbr_deal_finder-0.3.3/docs/desktop-app.md +133 -0
  7. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/pyproject.toml +10 -1
  8. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/scripts/packaging/create_dmg.sh +4 -1
  9. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/__init__.py +1 -1
  10. tbr_deal_finder-0.3.3/tbr_deal_finder/desktop_updater.py +71 -0
  11. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/gui/main.py +80 -27
  12. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/gui/pages/all_books.py +7 -0
  13. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/gui/pages/base_book_page.py +1 -2
  14. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/gui/pages/book_details.py +2 -3
  15. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/gui/pages/latest_deals.py +24 -20
  16. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/gui/pages/settings.py +1 -0
  17. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/retailer_deal.py +20 -29
  18. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/utils.py +28 -19
  19. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/uv.lock +1 -1
  20. tbr_deal_finder-0.3.1/docs/desktop-app.md +0 -251
  21. tbr_deal_finder-0.3.1/tbr_deal_finder/desktop_updater.py +0 -147
  22. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/.github/workflows/publish-to-pypi.yaml +0 -0
  23. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/.gitignore +0 -0
  24. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/.python-version +0 -0
  25. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/DESIGN.md +0 -0
  26. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/LICENSE +0 -0
  27. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/README.md +0 -0
  28. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/assets/icon.ico +0 -0
  29. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/assets/icon.png +0 -0
  30. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/docs/development.md +0 -0
  31. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/docs/python-cli.md +0 -0
  32. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/__main__.py +0 -0
  33. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/book.py +0 -0
  34. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/cli.py +0 -0
  35. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/config.py +0 -0
  36. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/gui/__init__.py +0 -0
  37. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/gui/pages/__init__.py +0 -0
  38. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/gui/pages/all_deals.py +0 -0
  39. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/migrations.py +0 -0
  40. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/owned_books.py +0 -0
  41. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/queries/get_active_deals.sql +0 -0
  42. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/queries/get_deals_found_at.sql +0 -0
  43. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/queries/latest_deal_last_ran_most_recent_success.sql +0 -0
  44. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/queries/latest_unknown_book_sync.sql +0 -0
  45. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/retailer/__init__.py +0 -0
  46. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/retailer/amazon.py +0 -0
  47. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/retailer/amazon_custom_auth.py +0 -0
  48. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/retailer/audible.py +0 -0
  49. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/retailer/chirp.py +0 -0
  50. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/retailer/kindle.py +0 -0
  51. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/retailer/librofm.py +0 -0
  52. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/retailer/models.py +0 -0
  53. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/tracked_books.py +0 -0
  54. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder/version_check.py +0 -0
  55. {tbr_deal_finder-0.3.1 → tbr_deal_finder-0.3.3}/tbr_deal_finder.py +0 -0
@@ -13,11 +13,12 @@ jobs:
13
13
  env:
14
14
  HAS_MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE != '' }}
15
15
  HAS_MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD != '' }}
16
-
16
+ VERSION: ${{ github.event.release.tag_name }}
17
+
17
18
  steps:
18
19
  - name: Checkout code
19
20
  uses: actions/checkout@v4
20
-
21
+
21
22
  - name: Set up Python
22
23
  uses: actions/setup-python@v5
23
24
  with:
@@ -25,7 +26,7 @@ jobs:
25
26
 
26
27
  - name: Install uv
27
28
  uses: astral-sh/setup-uv@v5
28
-
29
+
29
30
  - name: Install project dependencies
30
31
  run: |
31
32
  uv sync
@@ -34,7 +35,7 @@ jobs:
34
35
  uses: maxim-lobanov/setup-xcode@v1
35
36
  with:
36
37
  xcode-version: latest-stable
37
-
38
+
38
39
  - name: Set up CocoaPods
39
40
  uses: maxim-lobanov/setup-cocoapods@v1
40
41
  with:
@@ -43,44 +44,76 @@ jobs:
43
44
  - name: Import Code Signing Certificate (if available)
44
45
  if: ${{ env.HAS_MACOS_CERTIFICATE == 'true' && env.HAS_MACOS_CERTIFICATE_PASSWORD == 'true' }}
45
46
  run: |
47
+ set -e # Exit on any error
48
+
46
49
  # Create temporary keychain
47
- security create-keychain -p "temp_keychain_password" temp.keychain
48
- security default-keychain -s temp.keychain
49
- security unlock-keychain -p "temp_keychain_password" temp.keychain
50
+ KEYCHAIN_PASSWORD="temp_keychain_password"
51
+ KEYCHAIN_NAME="temp.keychain-db"
52
+
53
+ security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME"
54
+ security default-keychain -s "$KEYCHAIN_NAME"
55
+ security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME"
56
+ security set-keychain-settings -t 3600 -u "$KEYCHAIN_NAME"
50
57
 
51
- # Import certificate
58
+ # Decode PKCS12 certificate
52
59
  echo "${{ secrets.MACOS_CERTIFICATE }}" | base64 --decode > certificate.p12
53
- security import certificate.p12 -k temp.keychain -P "${{ secrets.MACOS_CERTIFICATE_PASSWORD }}" -T /usr/bin/codesign
60
+
61
+ # Verify file exists and has content
62
+ if [ ! -s certificate.p12 ]; then
63
+ echo "❌ Certificate file is empty or missing"
64
+ exit 1
65
+ fi
66
+
67
+ # Import PKCS12 to keychain (specifying format explicitly is required)
68
+ echo "Importing certificate..."
69
+ security import certificate.p12 \
70
+ -k "$KEYCHAIN_NAME" \
71
+ -P "${{ secrets.MACOS_CERTIFICATE_PASSWORD }}" \
72
+ -f pkcs12 \
73
+ -T /usr/bin/codesign
54
74
 
55
75
  # Allow codesign to access the certificate
56
- security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "temp_keychain_password" temp.keychain
76
+ security set-key-partition-list \
77
+ -S apple-tool:,apple:,codesign: \
78
+ -s \
79
+ -k "$KEYCHAIN_PASSWORD" \
80
+ "$KEYCHAIN_NAME" 2>/dev/null || true
57
81
 
58
82
  # Set code signing identity
59
83
  if [ -n "${{ secrets.CODESIGN_IDENTITY }}" ]; then
60
- # Use provided identity name
61
84
  echo "CODESIGN_IDENTITY=${{ secrets.CODESIGN_IDENTITY }}" >> $GITHUB_ENV
62
85
  echo "✅ Code signing certificate imported: ${{ secrets.CODESIGN_IDENTITY }}"
63
86
  else
64
87
  # Auto-detect identity from certificate
65
- CERT_IDENTITY=$(security find-identity -v -p codesigning temp.keychain | head -1 | grep -o '"[^"]*"' | tr -d '"')
88
+ CERT_IDENTITY=$(security find-identity -v -p codesigning "$KEYCHAIN_NAME" | grep -v "0 valid identities" | head -1 | awk -F'"' '{print $2}')
89
+ if [ -z "$CERT_IDENTITY" ]; then
90
+ # Fallback to any identity if no codesigning specific one found
91
+ CERT_IDENTITY=$(security find-identity -v "$KEYCHAIN_NAME" | grep -v "0 valid identities" | head -1 | awk -F'"' '{print $2}')
92
+ fi
93
+
94
+ if [ -z "$CERT_IDENTITY" ]; then
95
+ echo "❌ No signing identity found"
96
+ exit 1
97
+ fi
98
+
66
99
  echo "CODESIGN_IDENTITY=$CERT_IDENTITY" >> $GITHUB_ENV
67
100
  echo "✅ Code signing certificate imported (auto-detected): $CERT_IDENTITY"
68
101
  fi
69
102
 
70
- # Clean up certificate file
103
+ # Clean up
71
104
  rm -f certificate.p12
72
-
105
+
73
106
  - name: Build DMG
74
107
  run: |
75
108
  make build-mac
76
-
109
+
77
110
  - name: Cleanup Code Signing
78
111
  if: ${{ env.HAS_MACOS_CERTIFICATE == 'true' && env.HAS_MACOS_CERTIFICATE_PASSWORD == 'true' }}
79
112
  run: |
80
113
  # Remove temporary keychain
81
114
  security delete-keychain temp.keychain || true
82
115
  echo "🧹 Cleaned up code signing keychain"
83
-
116
+
84
117
  - name: Find and verify DMG was created
85
118
  id: find-dmg
86
119
  run: |
@@ -95,7 +128,7 @@ jobs:
95
128
  echo "✅ DMG file created successfully: $DMG_FILE"
96
129
  echo "dmg_path=$DMG_FILE" >> $GITHUB_OUTPUT
97
130
  echo "dmg_name=$(basename $DMG_FILE)" >> $GITHUB_OUTPUT
98
-
131
+
99
132
  - name: Get DMG info
100
133
  id: dmg-info
101
134
  run: |
@@ -103,7 +136,7 @@ jobs:
103
136
  echo "size=$DMG_SIZE" >> $GITHUB_OUTPUT
104
137
  echo "📦 DMG Size: $DMG_SIZE"
105
138
  echo "📦 DMG Name: ${{ steps.find-dmg.outputs.dmg_name }}"
106
-
139
+
107
140
  - name: Upload DMG to Release
108
141
  env:
109
142
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -111,5 +144,4 @@ jobs:
111
144
  gh release upload ${{ github.event.release.tag_name }} \
112
145
  "${{ steps.find-dmg.outputs.dmg_path }}" \
113
146
  --clobber \
114
- --repo ${{ github.repository }}
115
-
147
+ --repo ${{ github.repository }}
@@ -138,7 +138,7 @@ jobs:
138
138
  DefaultGroupName=${{ env.APP_DISPLAY_NAME }}
139
139
  AllowNoIcons=yes
140
140
  OutputDir=.
141
- OutputBaseFilename=${{ env.APP_FILENAME_BASE }}-${{ steps.app-info.outputs.app_version }}-Setup
141
+ OutputBaseFilename=${{ env.APP_FILENAME_BASE }}-${{ steps.app-info.outputs.app_version }}-Windows-Setup
142
142
  SetupIconFile=assets\icon.ico
143
143
  Compression=lzma2/normal
144
144
  SolidCompression=no
@@ -184,13 +184,13 @@ jobs:
184
184
  with:
185
185
  certificate: '${{ secrets.WINDOWS_CERTIFICATE }}'
186
186
  password: '${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}'
187
- files: '${{ env.APP_FILENAME_BASE }}-${{ steps.app-info.outputs.app_version }}-Setup.exe'
187
+ files: '${{ env.APP_FILENAME_BASE }}-${{ steps.app-info.outputs.app_version }}-Windows-Setup.exe'
188
188
 
189
189
  - name: Upload Installer to Release
190
190
  env:
191
191
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
192
192
  run: |
193
- $installerFile = "${{ env.APP_FILENAME_BASE }}-${{ steps.app-info.outputs.app_version }}-Setup.exe"
193
+ $installerFile = "${{ env.APP_FILENAME_BASE }}-${{ steps.app-info.outputs.app_version }}-Windows-Setup.exe"
194
194
 
195
195
  gh release upload ${{ github.event.release.tag_name }} `
196
196
  $installerFile `
@@ -3,6 +3,29 @@
3
3
 
4
4
  ---
5
5
 
6
+ ## 0.3.3 (September 10, 2025)
7
+
8
+ Notes:
9
+ * Improved updater for Mac app
10
+
11
+ BUG FIXES:
12
+ * Fixed Mac app cert
13
+ * Fixed issue where pricing graph points were running off graph
14
+
15
+ ---
16
+
17
+ ## 0.3.2 (September 8, 2025)
18
+
19
+ Notes:
20
+ * Disable nav bar buttons when performing certain operations
21
+ * Added config check to CLI location when running the desktop app for backwards compatibility
22
+ * Improved performance when retrieving "latest deals"
23
+
24
+ BUG FIXES:
25
+ * Fixed issue with scroll bar in the "all deals" page
26
+
27
+ ---
28
+
6
29
  ## 0.3.1 (September 5, 2025)
7
30
 
8
31
  Notes:
@@ -31,7 +31,7 @@ BUILD_SCRIPT := scripts/packaging/build_cross_platform.py
31
31
  # Build self-signed macOS DMG (recommended)
32
32
  build-mac:
33
33
  @echo "🍎 Building app"
34
- uv run flet build macos --output ${DIST_DIR}/app/
34
+ NONINTERACTIVE=1 NO_COLOR=1 TERM=dumb CI=true uv run flet build macos --output ${DIST_DIR}/app/
35
35
  @echo ""
36
36
  @echo "📦 Creating self-signed macOS DMG for app"
37
37
  bash scripts/packaging/create_dmg.sh
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tbr-deal-finder
3
- Version: 0.3.1
3
+ Version: 0.3.3
4
4
  Summary: Track price drops and find deals on books in your TBR list across audiobook and ebook formats.
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -0,0 +1,133 @@
1
+ # Desktop App Guide
2
+
3
+ Everything you need to know about installing, using, and updating the TBR Deal Finder desktop application.
4
+
5
+ ## Installation
6
+
7
+ The desktop app provides a beautiful graphical interface for managing your book deals without any command line knowledge required.
8
+
9
+ ### 🍎 macOS Installation
10
+
11
+ #### Download & Install
12
+ 1. Go to the [latest release](https://github.com/yourusername/tbr-deal-finder/releases/latest)
13
+ 2. Download `TBRDealFinder-{version}-macOS.dmg`
14
+ 3. **Open the DMG**: Double-click the downloaded `.dmg` file
15
+ 4. **Handle Security Warning**: macOS will show "Cannot verify developer"
16
+ - Click Done to handle the warning before continuing with the install
17
+ - [Follow guide from Apple](https://support.apple.com/guide/mac-help/open-a-mac-app-from-an-unknown-developer-mh40616/mac)
18
+ 5. **Install**: Drag the TBR Deal Finder app to your Applications folder
19
+ 6. **Launch**: Double-click the app in Applications
20
+ 7. **Wait for loader**: The app has a special loader and can take a minute to load for the first time
21
+
22
+ #### Troubleshooting macOS
23
+ - **"App can't be opened"**: Use right-click → Open instead of double-clicking
24
+ - **Still getting warnings**: Go to System Preferences → Security & Privacy → General → Click "Open Anyway"
25
+
26
+ ### 🪟 Windows Installation
27
+
28
+ #### Download & Install
29
+ 1. Go to the [latest release](https://github.com/yourusername/tbr-deal-finder/releases/latest)
30
+ 2. Download `TBRDealFinder-{version}-Windows.exe`
31
+ 3. **Run the Installer**: Double-click the downloaded `.exe` file
32
+ 4. **Handle Security Warning**: Windows will show "Unknown publisher"
33
+ - **Solution**: Click "More info" → Click "Run anyway"
34
+ 5. **Install**: Follow the installation wizard
35
+ 6. **Launch**: The app will be available in your Start Menu or Desktop
36
+ 7. **Wait for loader**: The app has a special loader and can take a minute to load for the first time
37
+
38
+ #### Troubleshooting Windows
39
+ - **Windows Defender blocks**: Click "More info" → "Run anyway"
40
+ - **Still blocked**: Temporarily disable real-time protection, install, then re-enable
41
+
42
+ ## 🎯 First Time Setup
43
+
44
+ ### Getting Your Reading Lists
45
+ Before using the app, export your reading lists:
46
+
47
+ #### StoryGraph Export
48
+ 1. Open [StoryGraph](https://app.thestorygraph.com/)
49
+ 2. Click your profile icon → "Manage Account"
50
+ 3. Scroll to "Manage Your Data" → "Export StoryGraph Library"
51
+ 4. Click "Generate export" → Wait and refresh → Download CSV
52
+
53
+ #### Goodreads Export
54
+ 1. Visit [Goodreads Export](https://www.goodreads.com/review/import)
55
+ 2. Click "Export Library" → Wait for email → Download CSV
56
+
57
+ #### Custom CSV
58
+ Create your own with these columns:
59
+ - `Title` (required)
60
+ - `Authors` (required)
61
+ - `Read Status` (optional: set to "to-read" for tracking)
62
+
63
+ ### Setup Wizard
64
+ 1. **Launch the App** for the first time
65
+ 2. **Follow the Setup Wizard**:
66
+ - Upload your CSV file(s)
67
+ - Select your country/region
68
+ - Set maximum price for deals
69
+ - Set minimum discount percentage
70
+ 3. **Start Finding Deals**: The app begins searching automatically
71
+
72
+ ## 🔄 Updating the Desktop App
73
+
74
+ ### Checking for Updates
75
+ Currently, updates require manual download:
76
+ 1. **Check Current Version**: Look in Settings/About section
77
+ 2. **Visit Releases**: Go to [latest releases](https://github.com/yourusername/tbr-deal-finder/releases/latest)
78
+ 3. **Compare Versions**: See if a newer version is available
79
+
80
+ ### Installing Updates
81
+
82
+ #### All Platforms
83
+ 1. **Download Latest Version**:
84
+ - macOS: `TBRDealFinder-{version}-macOS.dmg`
85
+ - Windows: `TBRDealFinder-{version}-Windows.exe`
86
+ 2. **Install Over Existing**: Follow same installation steps
87
+ - If prompted, choose to replace files or overwrite the existing application
88
+ 3. **Preserve Settings**: Your configuration and data are automatically preserved
89
+ 4. **Verify Update**: Check version in Settings after installation
90
+
91
+ ## ❓ Troubleshooting
92
+
93
+ ### App Won't Launch
94
+ - **macOS**: Right-click app → Open, check Security & Privacy settings
95
+ - **Windows**: Run as administrator, check Windows Defender
96
+
97
+ ### No Deals Found
98
+ - **Check CSV Format**: Ensure titles and authors are correct
99
+ - **Adjust Filters**: Lower discount threshold or raise price limit
100
+ - **Wait**: Deals fluctuate - check back regularly
101
+
102
+ ### Performance Issues
103
+ #### Initial Run
104
+ When the app initially starts the app may be white up to a minute while the tbr deal finder package loads.
105
+
106
+ #### Getting latest deals
107
+ Getting pricing info for Kindle can take up to a second per book.
108
+ It doesn't sound like a lot, but if you have 400 books in your tbr it may take around 7 minutes.
109
+ If you're not checking for deals on Kindle, getting deals should only take a couple minutes.
110
+ Don't close the app while getting deals or progress may be lost.
111
+
112
+ #### Still having issues
113
+ - **Restart App**: Close and reopen to clear memory
114
+ - **Update**: Ensure you're running the latest version
115
+ - **System**: Close other applications to free resources
116
+
117
+ ## 🆘 Getting Help
118
+
119
+ ### Community Support
120
+ 1. **GitHub Issues**: [Report bugs or ask questions](https://github.com/yourusername/tbr-deal-finder/issues)
121
+ 2. **Search First**: Someone might have had the same issue
122
+ 3. **Provide Details**: Include OS version, error messages, screenshots
123
+
124
+ ### What to Include in Bug Reports
125
+ - **Operating System**: macOS 12.1, Windows 11, Ubuntu 22.04, etc.
126
+ - **App Version**: Found in Settings/About
127
+ - **Error Messages**: Exact text of any errors
128
+ - **Screenshots**: Visual problems are easier to diagnose
129
+ - **Steps to Reproduce**: What you did when the problem occurred
130
+
131
+ ---
132
+
133
+ **Ready to discover amazing book deals? Download the desktop app and start saving money on your reading list!** 📚💰
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tbr-deal-finder"
3
- version = "0.3.1"
3
+ version = "0.3.3"
4
4
  description = "Track price drops and find deals on books in your TBR list across audiobook and ebook formats."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -30,6 +30,15 @@ product = "TBR Deal Finder"
30
30
  app.module = "tbr_deal_finder"
31
31
  app.path = "."
32
32
 
33
+ # Dependency locks
34
+ # Fix for https://github.com/flet-dev/flet/issues/5630
35
+ android.min_sdk_version = 24
36
+
37
+ [tool.flet.flutter.pubspec.dependency_overrides]
38
+ # Dependency locks
39
+ # Fix for https://github.com/flet-dev/flet/issues/5630
40
+ webview_flutter_android = "4.10.1"
41
+
33
42
  [tool.setuptools.package-data]
34
43
  "tbr-deal-finder" = [
35
44
  "queries/**/*.sql",
@@ -13,11 +13,14 @@
13
13
 
14
14
  DIST_DIR="gui_dist"
15
15
  APP_NAME="TBR Deal Finder"
16
- DMG_NAME="TBR-Deal-Finder-0.2.1"
16
+ DMG_NAME="TBR-Deal-Finder-${VERSION#v}-mac"
17
17
  VOLUME_NAME="TBR Deal Finder"
18
18
  SOURCE_APP="${DIST_DIR}/app/${APP_NAME}.app"
19
19
  OUTPUT_DMG="${DIST_DIR}/${DMG_NAME}.dmg"
20
20
 
21
+ echo "📋 Using version: ${VERSION}"
22
+ echo "📦 DMG name will be: ${DMG_NAME}.dmg"
23
+
21
24
  # Determine signing approach
22
25
  if [ -z "$CODESIGN_IDENTITY" ]; then
23
26
  # Default to ad-hoc signing for local development
@@ -1,5 +1,5 @@
1
1
  from pathlib import Path
2
2
 
3
- __VERSION__ = "0.3.1"
3
+ __VERSION__ = "0.3.3"
4
4
 
5
5
  QUERY_PATH = Path(__file__).parent.joinpath("queries")
@@ -0,0 +1,71 @@
1
+ """
2
+ Desktop application update checker and handler.
3
+ For packaged desktop applications (.dmg/.exe).
4
+ """
5
+ import json
6
+ import logging
7
+ import os
8
+ import platform
9
+ import subprocess
10
+ import tempfile
11
+ import webbrowser
12
+ from pathlib import Path
13
+ from typing import Optional, Dict, Any
14
+
15
+ import requests
16
+ from packaging import version
17
+
18
+ from tbr_deal_finder import __VERSION__
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+ class DesktopUpdater:
23
+ """Handle updates for packaged desktop applications."""
24
+
25
+ def __init__(self, github_repo: str = "WillNye/tbr-deal-finder"):
26
+ self.github_repo = github_repo
27
+ self.current_version = __VERSION__
28
+ self.platform = platform.system().lower()
29
+
30
+ def check_for_updates(self) -> Optional[Dict[str, Any]]:
31
+ """
32
+ Check GitHub releases for newer versions.
33
+ Returns dict with update info or None if no update available.
34
+ """
35
+ try:
36
+ # Check GitHub releases API
37
+ response = requests.get(
38
+ f"https://api.github.com/repos/{self.github_repo}/releases/latest",
39
+ timeout=5
40
+ )
41
+ response.raise_for_status()
42
+
43
+ release_data = response.json()
44
+ latest_version = release_data["tag_name"].lstrip("v")
45
+ if version.parse(latest_version) > version.parse(self.current_version):
46
+ download_url = release_data["html_url"]
47
+ if self.platform == "darwin":
48
+ for asset in release_data["assets"]:
49
+ if asset["browser_download_url"].endswith(".dmg"):
50
+ download_url = asset["browser_download_url"]
51
+
52
+ return {
53
+ "version": latest_version,
54
+ "download_url": download_url,
55
+ "release_notes": release_data.get("body", ""),
56
+ }
57
+
58
+ return None
59
+
60
+ except Exception as e:
61
+ logger.error(f"Failed to check updates for {self.github_repo}: {e}")
62
+ return None
63
+
64
+
65
+ # Global instance
66
+ desktop_updater = DesktopUpdater()
67
+
68
+
69
+ def check_for_desktop_updates() -> Optional[Dict[str, Any]]:
70
+ """Convenience function to check for updates."""
71
+ return desktop_updater.check_for_updates()
@@ -1,6 +1,10 @@
1
1
  import asyncio
2
2
  import os
3
3
  import base64
4
+ import subprocess
5
+ import sys
6
+ from datetime import datetime
7
+ from pathlib import Path
4
8
 
5
9
  import flet as ft
6
10
 
@@ -17,6 +21,7 @@ from tbr_deal_finder.gui.pages.all_deals import AllDealsPage
17
21
  from tbr_deal_finder.gui.pages.latest_deals import LatestDealsPage
18
22
  from tbr_deal_finder.gui.pages.all_books import AllBooksPage
19
23
  from tbr_deal_finder.gui.pages.book_details import BookDetailsPage
24
+ from tbr_deal_finder.utils import get_duckdb_conn, get_latest_deal_last_ran
20
25
 
21
26
 
22
27
  class TBRDealFinderApp:
@@ -26,7 +31,9 @@ class TBRDealFinderApp:
26
31
  self.current_page = "all_deals"
27
32
  self.selected_book = None
28
33
  self.update_info = None # Store update information
29
-
34
+ self.nav_disabled = False # Track navigation disabled state
35
+ self._last_run_time = None
36
+
30
37
  # Initialize pages
31
38
  self.settings_page = SettingsPage(self)
32
39
  self.all_deals_page = AllDealsPage(self)
@@ -258,6 +265,16 @@ class TBRDealFinderApp:
258
265
 
259
266
  def nav_changed(self, e):
260
267
  """Handle navigation rail selection changes"""
268
+ # Prevent navigation if disabled
269
+ if self.nav_disabled:
270
+ # Reset to current page selection to prevent visual change
271
+ current_indices = {"all_deals": 0, "latest_deals": 1, "all_books": 2}
272
+ self.nav_rail.selected_index = current_indices.get(self.current_page, 0)
273
+ # Reapply disabled state after page update
274
+ self.nav_rail.disabled = True
275
+ self.page.update()
276
+ return
277
+
261
278
  destinations = ["all_deals", "latest_deals", "all_books"]
262
279
  if e.control.selected_index < len(destinations):
263
280
  self.current_page = destinations[e.control.selected_index]
@@ -284,10 +301,26 @@ class TBRDealFinderApp:
284
301
  self.all_books_page.refresh_page_state()
285
302
 
286
303
  def refresh_all_pages(self):
287
- """Refresh all pages by clearing their state and reloading data"""
304
+ """Refresh all pages except all_books_page by clearing their state and reloading data"""
288
305
  self.all_deals_page.refresh_page_state()
289
306
  self.latest_deals_page.refresh_page_state()
290
- self.all_books_page.refresh_page_state()
307
+
308
+ def disable_navigation(self):
309
+ """Disable navigation rail during background operations"""
310
+ self.nav_disabled = True
311
+ if hasattr(self, 'nav_rail'):
312
+ self.nav_rail.disabled = True
313
+ self.page.update()
314
+
315
+ def enable_navigation(self):
316
+ """Enable navigation rail after background operations complete"""
317
+ if not self.nav_disabled:
318
+ return
319
+
320
+ self.nav_disabled = False
321
+ if hasattr(self, 'nav_rail'):
322
+ self.nav_rail.disabled = False
323
+ self.page.update()
291
324
 
292
325
  def get_current_page_content(self):
293
326
  """Get content for the current page"""
@@ -309,6 +342,7 @@ class TBRDealFinderApp:
309
342
 
310
343
  def get_config_prompt(self):
311
344
  """Show config setup prompt when no config exists"""
345
+ self.disable_navigation()
312
346
  return ft.Container(
313
347
  content=ft.Column([
314
348
  ft.Icon(ft.Icons.SETTINGS, size=64, color=ft.Colors.GREY_400),
@@ -592,10 +626,6 @@ class TBRDealFinderApp:
592
626
  def close_dialog(e):
593
627
  dialog.open = False
594
628
  self.page.update()
595
-
596
- def view_release_and_close(e):
597
- self.view_release_notes(e)
598
- close_dialog(e)
599
629
 
600
630
  def download_and_close(e):
601
631
  self.download_update(e)
@@ -609,10 +639,13 @@ class TBRDealFinderApp:
609
639
  ], spacing=10),
610
640
  content=ft.Column([
611
641
  ft.Text(f"Version {self.update_info['version']} is now available!"),
612
- ft.Text("Would you like to download the update?", color=ft.Colors.GREY_600)
613
- ], spacing=10, tight=True),
642
+ ft.Divider(),
643
+ ft.Text(
644
+ self.update_info.get('release_notes', 'No release notes available.'),
645
+ selectable=True
646
+ ),
647
+ ], scroll=ft.ScrollMode.AUTO, spacing=10, tight=True),
614
648
  actions=[
615
- ft.TextButton("View Release Notes", on_click=view_release_and_close),
616
649
  ft.ElevatedButton("Download Update", on_click=download_and_close),
617
650
  ft.TextButton("Later", on_click=close_dialog),
618
651
  ],
@@ -646,24 +679,36 @@ class TBRDealFinderApp:
646
679
  dialog.open = True
647
680
  self.page.update()
648
681
 
649
- def view_release_notes(self, e):
650
- """Open release notes in browser."""
651
- if self.update_info:
652
- import webbrowser
653
- webbrowser.open(self.update_info['release_url'])
654
-
655
682
  def download_update(self, e):
656
683
  """Handle update download."""
657
684
  if not self.update_info or not self.update_info.get('download_url'):
658
685
  return
659
-
660
- # For now, open download URL in browser
661
- # In a more advanced implementation, you could download in-app
662
- import webbrowser
663
- webbrowser.open(self.update_info['download_url'])
664
-
665
- # Show download instructions
666
- self.show_download_instructions()
686
+
687
+ if sys.platform == "darwin":
688
+ dmg_path = Path(
689
+ f"~/Downloads/TBR-Deal-Finder-{self.update_info['version']}-mac.dmg"
690
+ ).expanduser()
691
+
692
+ # Show download instructions
693
+ self.show_download_instructions()
694
+
695
+ if not dmg_path.exists():
696
+ # Using curl or urllib to download to prevent Mac warning
697
+ subprocess.run([
698
+ "curl", "-L",
699
+ self.update_info['download_url'],
700
+ "-o", dmg_path
701
+ ])
702
+
703
+ subprocess.run(["open", dmg_path])
704
+ else:
705
+ # For now, open download URL in browser
706
+ # In a more advanced implementation, you could download in-app
707
+ import webbrowser
708
+ webbrowser.open(self.update_info['download_url'])
709
+
710
+ # Show download instructions
711
+ self.show_download_instructions()
667
712
 
668
713
  def show_download_instructions(self):
669
714
  """Show instructions for installing the downloaded update."""
@@ -672,7 +717,7 @@ class TBRDealFinderApp:
672
717
  self.page.update()
673
718
 
674
719
  instructions = {
675
- "darwin": "1. Download will start in your browser\n2. Open the downloaded .dmg file\n3. Drag the app to Applications folder\n4. Restart the application",
720
+ "darwin": "1. Wait for the update to download (can take a minute or 2)\n2. Close TBR Deal Finder\n3. Once the installer opens, drag the app to Applications folder\n3. When prompted, select Replace\n4. Restart the application",
676
721
  "windows": "1. Download will start in your browser\n2. Run the downloaded .exe installer\n3. Follow the installation wizard\n4. Restart the application",
677
722
  }
678
723
 
@@ -703,12 +748,20 @@ class TBRDealFinderApp:
703
748
  dialog.open = True
704
749
  self.page.update()
705
750
 
706
-
707
-
708
751
  def check_for_updates_button(self):
709
752
  """Check for updates when button is clicked."""
710
753
  self.check_for_updates_manual()
711
754
 
755
+ def update_last_run_time(self):
756
+ db_conn = get_duckdb_conn()
757
+ self._last_run_time = get_latest_deal_last_ran(db_conn)
758
+
759
+ def get_last_run_time(self) -> datetime:
760
+ if not self._last_run_time:
761
+ self.update_last_run_time()
762
+
763
+ return self._last_run_time
764
+
712
765
 
713
766
  def main():
714
767
  """Main entry point for the GUI application"""