├── LOG.txt ├── linux ├── .gitignore ├── assets │ └── otzaria.png ├── main.cc ├── flutter │ ├── generated_plugin_registrant.h │ └── generated_plugins.cmake ├── my_application.h └── packaging │ ├── deb │ └── make_config.yaml │ ├── rpm │ └── make_config.yaml │ └── README.md ├── version.json ├── analysis_options.yaml ├── sivan22.pfx ├── ios ├── Runner │ ├── Runner-Bridging-Header.h │ ├── Assets.xcassets │ │ ├── LaunchImage.imageset │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ ├── README.md │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-50x50@1x.png │ │ │ ├── Icon-App-50x50@2x.png │ │ │ ├── Icon-App-57x57@1x.png │ │ │ ├── Icon-App-57x57@2x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-72x72@1x.png │ │ │ ├── Icon-App-72x72@2x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ ├── AppDelegate.swift │ ├── Base.lproj │ │ └── Main.storyboard │ └── Info.plist ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── Runner.xcodeproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ └── IDEWorkspaceChecks.plist ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ └── IDEWorkspaceChecks.plist ├── RunnerTests │ └── RunnerTests.swift ├── .gitignore └── Podfile ├── images ├── logo.png ├── Orayta.png ├── dicta_logo.jpg ├── screenshot.png ├── toratemet.png ├── safria logo.png ├── OnYourWay_logo.jpg ├── JewishBook-logo-שקוף.png ├── white_sketch128x128.ico ├── Project Ben-Yehuda logo.jpg └── logo.svg ├── sivan22.pfx.bak ├── web ├── favicon.png ├── icons │ ├── Icon-192.png │ ├── Icon-512.png │ ├── Icon-maskable-192.png │ └── Icon-maskable-512.png ├── manifest.json └── index.html ├── assets ├── icon │ ├── icon.png │ ├── זכור ושמור.png │ ├── שמור וזכור.png │ ├── logo_nedarim.png │ ├── שמור וזכור מקור.png │ ├── shamor_zachor_with_v.png │ └── memorial_candle.svg ├── logos │ ├── otzar.ico │ └── hebrew_books.png └── shamor_zachor │ └── data │ └── yerushalmi.json ├── fonts ├── ShofarRegular.ttf ├── Tinos-Regular.ttf ├── KeterYG-Medium.ttf ├── FrankRuehlCLM-Bold.ttf ├── FrankRuehlCLM-Medium.ttf ├── TaameyAshkenaz-Medium.ttf ├── TaameyDavidCLM-Medium.ttf ├── Rubik-VariableFont_wght.ttf ├── NotoRashiHebrew-VariableFont_wght.ttf └── NotoSerifHebrew-VariableFont_wdth,wght.ttf ├── installer ├── white_sketch128x128.ico ├── .gitignore ├── uninstall_msix.ps1 ├── reset_settings.ps1 └── otzaria.iss ├── android ├── gradle.properties ├── app │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ └── launcher_icon.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ └── launcher_icon.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ └── launcher_icon.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ └── launcher_icon.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ └── launcher_icon.png │ │ │ │ ├── xml │ │ │ │ │ └── network_security_config.xml │ │ │ │ ├── drawable │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable-v21 │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── values │ │ │ │ │ └── styles.xml │ │ │ │ └── values-night │ │ │ │ │ └── styles.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── otzaria │ │ │ │ │ └── MainActivity.kt │ │ │ └── AndroidManifest.xml │ │ ├── profile │ │ │ └── AndroidManifest.xml │ │ └── debug │ │ │ └── AndroidManifest.xml │ └── build.gradle ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── .gitignore ├── settings.gradle └── build.gradle ├── macos ├── Runner │ ├── Configs │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ ├── Warnings.xcconfig │ │ └── AppInfo.xcconfig │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── app_icon_16.png │ │ │ ├── app_icon_32.png │ │ │ ├── app_icon_64.png │ │ │ ├── app_icon_1024.png │ │ │ ├── app_icon_128.png │ │ │ ├── app_icon_256.png │ │ │ ├── app_icon_512.png │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── MainFlutterWindow.swift │ ├── Release.entitlements │ ├── DebugProfile.entitlements │ └── Info.plist ├── .gitignore ├── Flutter │ ├── Flutter-Debug.xcconfig │ └── Flutter-Release.xcconfig ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── Runner.xcodeproj │ └── project.xcworkspace │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── RunnerTests │ └── RunnerTests.swift └── Podfile ├── windows ├── runner │ ├── resources │ │ └── app_icon.ico │ ├── resource.h │ ├── utils.h │ ├── runner.exe.manifest │ ├── flutter_window.h │ ├── main.cpp │ ├── CMakeLists.txt │ └── utils.cpp ├── .gitignore └── flutter │ ├── generated_plugin_registrant.h │ ├── generated_plugins.cmake │ └── generated_plugin_registrant.cc ├── devtools_options.yaml ├── test ├── unit │ ├── mocks │ │ ├── mock_settings_wrapper.dart │ │ ├── mock_settings.dart │ │ ├── mock_settings_repository.dart │ │ ├── mock_settings.mocks.dart │ │ └── mock_settings_wrapper.mocks.dart │ └── settings │ │ └── history │ │ └── bookmark_model_test.dart ├── widget_test.dart ├── services │ └── data_collection_service_test.dart ├── calendar_cubit_test.dart └── models │ └── phone_report_data_test.dart ├── update_version.bat ├── lib ├── widgets │ ├── filter_list │ │ └── src │ │ │ ├── theme │ │ │ └── theme.dart │ │ │ ├── widget │ │ │ ├── search_field_widget.dart │ │ │ └── control_button.dart │ │ │ └── state │ │ │ └── filter_state.dart │ ├── password_dialog.dart │ └── reusable_items_dialog.dart ├── utils │ ├── extraction.dart │ ├── color_utils.dart │ └── settings_wrapper.dart ├── models │ └── isar_collections │ │ ├── line.dart │ │ └── ref.dart ├── history │ ├── history_dialog.dart │ ├── bloc │ │ ├── history_state.dart │ │ └── history_event.dart │ └── history_repository.dart ├── bookmarks │ ├── bloc │ │ ├── bookmark_state.dart │ │ └── bookmark_bloc.dart │ ├── bookmarks_dialog.dart │ └── repository │ │ └── bookmark_repository.dart ├── personal_notes │ ├── utils │ │ ├── note_collection_utils.dart │ │ └── note_text_utils.dart │ ├── personal_notes_system.dart │ └── bloc │ │ ├── personal_notes_state.dart │ │ └── personal_notes_event.dart ├── file_sync │ ├── file_sync_event.dart │ └── file_sync_state.dart ├── search │ ├── view │ │ ├── tantivy_search_field.dart │ │ └── full_text_search_screen.dart │ ├── models │ │ └── search_terms_model.dart │ └── bloc │ │ └── search_event.dart ├── text_book │ ├── models │ │ ├── search_results.dart │ │ └── commentator_group.dart │ ├── editing │ │ ├── helpers │ │ │ └── editor_settings_helper.dart │ │ ├── models │ │ │ ├── editor_settings.dart │ │ │ ├── editor_state.dart │ │ │ └── text_draft.dart │ │ └── repository │ │ │ └── overrides_repository.dart │ ├── view │ │ └── text_book_scaffold.dart │ └── text_book_repository.dart ├── navigation │ ├── navigation_repository.dart │ ├── bloc │ │ ├── navigation_event.dart │ │ ├── navigation_state.dart │ │ └── navigation_bloc.dart │ ├── about_dialog.dart │ └── favoriets_screen.dart ├── find_ref │ ├── find_ref_event.dart │ ├── find_ref_state.dart │ ├── find_ref_bloc.dart │ └── find_ref_repository.dart ├── app_bloc_observer.dart ├── daf_yomi │ └── calendar.dart ├── indexing │ └── bloc │ │ ├── indexing_event.dart │ │ └── indexing_state.dart ├── tabs │ ├── bloc │ │ ├── tabs_state.dart │ │ └── tabs_event.dart │ ├── tabs_repository.dart │ └── models │ │ └── tab.dart ├── data │ └── repository │ │ ├── base_list_repository.dart │ │ └── hive_list_repository.dart ├── empty_library │ └── bloc │ │ ├── empty_library_event.dart │ │ └── empty_library_state.dart ├── workspaces │ ├── workspace.dart │ ├── bloc │ │ ├── workspace_state.dart │ │ └── workspace_event.dart │ └── workspace_repository.dart ├── focus │ └── focus_repository.dart ├── library │ └── bloc │ │ └── library_event.dart └── core │ └── window_listener.dart ├── distribute_options.yaml ├── create.bat ├── .github ├── ISSUE_TEMPLATE │ ├── feature-request---.md │ └── bug-report---.md └── workflows │ └── claude.yml ├── packages └── shamor_zachor │ ├── lib │ ├── shamor_zachor.dart │ ├── shamor_zachor_config.dart │ └── utils │ │ ├── category_aliases.dart │ │ └── json_utils.dart │ ├── pubspec.yaml │ ├── test │ ├── category_aliases_test.dart │ └── shamor_zachor_test.dart │ └── assets │ └── data │ └── yerushalmi.json ├── .metadata ├── .gitignore ├── sivan22.crt ├── sivan22.crt.bak └── webhooks ├── main.py └── yemot.py /LOG.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /version.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.9.71" 3 | } -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | -------------------------------------------------------------------------------- /sivan22.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/sivan22.pfx -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/images/logo.png -------------------------------------------------------------------------------- /sivan22.pfx.bak: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/sivan22.pfx.bak -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/web/favicon.png -------------------------------------------------------------------------------- /images/Orayta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/images/Orayta.png -------------------------------------------------------------------------------- /assets/icon/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/assets/icon/icon.png -------------------------------------------------------------------------------- /images/dicta_logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/images/dicta_logo.jpg -------------------------------------------------------------------------------- /images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/images/screenshot.png -------------------------------------------------------------------------------- /images/toratemet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/images/toratemet.png -------------------------------------------------------------------------------- /assets/logos/otzar.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/assets/logos/otzar.ico -------------------------------------------------------------------------------- /fonts/ShofarRegular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/fonts/ShofarRegular.ttf -------------------------------------------------------------------------------- /fonts/Tinos-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/fonts/Tinos-Regular.ttf -------------------------------------------------------------------------------- /images/safria logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/images/safria logo.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/web/icons/Icon-512.png -------------------------------------------------------------------------------- /assets/icon/זכור ושמור.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/assets/icon/זכור ושמור.png -------------------------------------------------------------------------------- /assets/icon/שמור וזכור.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/assets/icon/שמור וזכור.png -------------------------------------------------------------------------------- /fonts/KeterYG-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/fonts/KeterYG-Medium.ttf -------------------------------------------------------------------------------- /images/OnYourWay_logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/images/OnYourWay_logo.jpg -------------------------------------------------------------------------------- /linux/assets/otzaria.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/linux/assets/otzaria.png -------------------------------------------------------------------------------- /assets/icon/logo_nedarim.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/assets/icon/logo_nedarim.png -------------------------------------------------------------------------------- /fonts/FrankRuehlCLM-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/fonts/FrankRuehlCLM-Bold.ttf -------------------------------------------------------------------------------- /assets/icon/שמור וזכור מקור.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/assets/icon/שמור וזכור מקור.png -------------------------------------------------------------------------------- /assets/logos/hebrew_books.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/assets/logos/hebrew_books.png -------------------------------------------------------------------------------- /fonts/FrankRuehlCLM-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/fonts/FrankRuehlCLM-Medium.ttf -------------------------------------------------------------------------------- /fonts/TaameyAshkenaz-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/fonts/TaameyAshkenaz-Medium.ttf -------------------------------------------------------------------------------- /fonts/TaameyDavidCLM-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/fonts/TaameyDavidCLM-Medium.ttf -------------------------------------------------------------------------------- /images/JewishBook-logo-שקוף.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/images/JewishBook-logo-שקוף.png -------------------------------------------------------------------------------- /images/white_sketch128x128.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/images/white_sketch128x128.ico -------------------------------------------------------------------------------- /web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /fonts/Rubik-VariableFont_wght.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/fonts/Rubik-VariableFont_wght.ttf -------------------------------------------------------------------------------- /installer/white_sketch128x128.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/installer/white_sketch128x128.ico -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4G 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | 5 | -------------------------------------------------------------------------------- /assets/icon/shamor_zachor_with_v.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/assets/icon/shamor_zachor_with_v.png -------------------------------------------------------------------------------- /images/Project Ben-Yehuda logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/images/Project Ben-Yehuda logo.jpg -------------------------------------------------------------------------------- /macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/dgph 7 | **/xcuserdata/ 8 | -------------------------------------------------------------------------------- /fonts/NotoRashiHebrew-VariableFont_wght.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/fonts/NotoRashiHebrew-VariableFont_wght.ttf -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /fonts/NotoSerifHebrew-VariableFont_wdth,wght.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/fonts/NotoSerifHebrew-VariableFont_wdth,wght.ttf -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/android/app/src/main/res/mipmap-hdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/android/app/src/main/res/mipmap-mdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otzaria/otzaria/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/otzaria/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.otzaria 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /devtools_options.yaml: -------------------------------------------------------------------------------- 1 | description: This file stores settings for Dart & Flutter DevTools. 2 | documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states 3 | extensions: 4 | -------------------------------------------------------------------------------- /linux/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/unit/mocks/mock_settings_wrapper.dart: -------------------------------------------------------------------------------- 1 | import 'package:mockito/annotations.dart'; 2 | import 'package:otzaria/utils/settings_wrapper.dart'; 3 | 4 | @GenerateNiceMocks([MockSpec()]) 5 | void main() {} 6 | -------------------------------------------------------------------------------- /test/unit/mocks/mock_settings.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_settings_screens/flutter_settings_screens.dart'; 2 | import 'package:mockito/annotations.dart'; 3 | 4 | @GenerateNiceMocks([MockSpec()]) 5 | void main() {} 6 | -------------------------------------------------------------------------------- /test/unit/mocks/mock_settings_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:mockito/annotations.dart'; 2 | import 'package:otzaria/settings/settings_repository.dart'; 3 | 4 | @GenerateNiceMocks([MockSpec()]) 5 | void main() {} 6 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip 6 | -------------------------------------------------------------------------------- /update_version.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | echo Running version update script... 3 | powershell -ExecutionPolicy Bypass -File update_version.ps1 4 | if %ERRORLEVEL% EQU 0 ( 5 | echo Version update completed successfully! 6 | ) else ( 7 | echo Version update failed! 8 | ) 9 | pause -------------------------------------------------------------------------------- /lib/widgets/filter_list/src/theme/theme.dart: -------------------------------------------------------------------------------- 1 | export 'choice_chip_theme.dart'; 2 | export 'contol_button_theme.dart'; 3 | export 'control_button_bar_theme.dart'; 4 | export 'filter_list_delegate_theme.dart'; 5 | export 'filter_list_theme.dart'; 6 | export 'header_theme.dart'; 7 | -------------------------------------------------------------------------------- /installer/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore Visual C++ Redistributable file (large file, should be downloaded separately) 2 | VisualCppRedist_AIO_x86_x64.exe 3 | 4 | # Ignore built installers (but not icon file) 5 | otzaria-*.exe 6 | 7 | # Ignore Inno Setup output directory if it exists 8 | Output/ 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /distribute_options.yaml: -------------------------------------------------------------------------------- 1 | output: dist/ 2 | releases: 3 | - name: production 4 | jobs: 5 | - name: release-linux-deb 6 | package: 7 | platform: linux 8 | target: deb 9 | - name: release-linux-rpm 10 | package: 11 | platform: linux 12 | target: rpm 13 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /macos/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import FlutterMacOS 2 | import Cocoa 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @main 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | 10 | override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { 11 | return true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /lib/utils/extraction.dart: -------------------------------------------------------------------------------- 1 | import 'package:otzaria/models/books.dart'; 2 | 3 | List getAllTopics(List books) { 4 | List topics = []; 5 | for (var book in books) { 6 | for (var topic in book.topics.split(', ')) { 7 | if (!topics.contains(topic)) { 8 | topics.add(topic); 9 | } 10 | } 11 | } 12 | topics.sort((a, b) => a.compareTo(b)); 13 | return topics; 14 | } 15 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /create.bat: -------------------------------------------------------------------------------- 1 | REM Clean Flutter build 2 | call flutter clean 3 | 4 | REM Build Flutter Windows app 5 | call flutter build windows 6 | 7 | REM Run Inno Setup scripts 8 | call iscc installer\otzaria.iss 9 | call iscc installer\otzaria_full.iss 10 | 11 | REM Build Flutter Android app 12 | call flutter build apk 13 | 14 | REM build Flutter linux binaries 15 | call flutter build linux 16 | 17 | echo Build and packaging complete. 18 | pause -------------------------------------------------------------------------------- /lib/models/isar_collections/line.dart: -------------------------------------------------------------------------------- 1 | import 'package:isar/isar.dart'; 2 | 3 | part 'line.g.dart'; 4 | 5 | @Collection() 6 | class Line { 7 | final int id; 8 | final String text; 9 | final String bookTitle; 10 | final String topics; 11 | final int index; 12 | 13 | Line( 14 | {required this.id, 15 | required this.text, 16 | required this.bookTitle, 17 | required this.topics, 18 | required this.index}); 19 | } 20 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @main 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /installer/uninstall_msix.ps1: -------------------------------------------------------------------------------- 1 | # Uninstall the MSIX package with the specified package name 2 | 3 | $packageName = "sivan22.Otzaria" 4 | 5 | # Get the package 6 | $package = Get-AppxPackage -Name $packageName 7 | 8 | if ($null -ne $package) { 9 | Write-Host "Uninstalling package: $packageName" 10 | Remove-AppxPackage -Package $package.PackageFullName 11 | Write-Host "Uninstallation complete." 12 | } else { 13 | Write-Host "Package '$packageName' not found." 14 | } -------------------------------------------------------------------------------- /lib/history/history_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:otzaria/history/history_screen.dart'; 3 | import 'package:otzaria/widgets/reusable_items_dialog.dart'; 4 | 5 | class HistoryDialog extends StatelessWidget { 6 | const HistoryDialog({super.key}); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return ReusableItemsDialog( 11 | title: 'היסטוריה', 12 | child: const HistoryView(), 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /linux/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/bookmarks/bloc/bookmark_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:otzaria/bookmarks/models/bookmark.dart'; 2 | 3 | class BookmarkState { 4 | final List bookmarks; 5 | 6 | BookmarkState({required this.bookmarks}); 7 | 8 | factory BookmarkState.initial() { 9 | return BookmarkState(bookmarks: const []); 10 | } 11 | 12 | BookmarkState copyWith({List? bookmarks}) { 13 | return BookmarkState( 14 | bookmarks: bookmarks ?? this.bookmarks, 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/bookmarks/bookmarks_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:otzaria/bookmarks/bookmark_screen.dart'; 3 | import 'package:otzaria/widgets/reusable_items_dialog.dart'; 4 | 5 | class BookmarksDialog extends StatelessWidget { 6 | const BookmarksDialog({super.key}); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return ReusableItemsDialog( 11 | title: 'סימניות', 12 | child: const BookmarkView(), 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/personal_notes/utils/note_collection_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:otzaria/personal_notes/models/personal_note.dart'; 2 | 3 | List sortPersonalNotes(List notes) { 4 | final located = notes.where((n) => n.hasLocation).toList() 5 | ..sort((a, b) => a.lineNumber!.compareTo(b.lineNumber!)); 6 | final missing = notes.where((n) => !n.hasLocation).toList() 7 | ..sort((a, b) => b.updatedAt.compareTo(a.updatedAt)); 8 | return [...located, ...missing]; 9 | } 10 | -------------------------------------------------------------------------------- /installer/reset_settings.ps1: -------------------------------------------------------------------------------- 1 | # Define the target directory path using environment variable 2 | $targetPath = "${env:APPDATA}\com.example\otzaria" 3 | 4 | # Check if the path exists 5 | if (Test-Path -Path $targetPath) { 6 | # Remove all items recursively (including subfolders) 7 | Remove-Item -Path $targetPath -Force -Recurse 8 | 9 | Write-Host "Successfully erased contents of '$targetPath'." 10 | } 11 | else { 12 | Write-Host "Directory '$targetPath' not found. Skipping deletion." 13 | } 14 | -------------------------------------------------------------------------------- /lib/models/isar_collections/ref.dart: -------------------------------------------------------------------------------- 1 | import 'package:isar/isar.dart'; 2 | 3 | part 'ref.g.dart'; 4 | 5 | @Collection() 6 | class Ref { 7 | @Id() 8 | final int id; 9 | final String ref; 10 | final String bookTitle; 11 | final int index; 12 | final bool pdfBook; 13 | final String? pdfPath; 14 | Ref( 15 | {required this.id, 16 | required this.ref, 17 | required this.bookTitle, 18 | required this.index, 19 | required this.pdfBook, 20 | this.pdfPath}); 21 | } 22 | -------------------------------------------------------------------------------- /test/unit/settings/history/bookmark_model_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:otzaria/bookmarks/models/bookmark.dart'; 3 | 4 | void main() { 5 | test('Bookmark.fromJson handles missing commentators field', () { 6 | final json = { 7 | 'ref': 'test ref', 8 | 'index': 1, 9 | 'book': {'title': 'Book A', 'type': 'TextBook'} 10 | }; 11 | final bookmark = Bookmark.fromJson(json); 12 | expect(bookmark.commentatorsToShow, isEmpty); 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request---.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Feature request \U0001F680 בקשה להוספת תכונה" 3 | about: Suggest an idea | רעיון לשיפור או שדרוג 4 | labels: enhancement 5 | 6 | --- 7 | 8 | ## Summary | סיכום 9 | Brief explanation of the feature. 10 | הסבר קצר של התכונה 11 | 12 | ### Basic example | דוגמה 13 | Include a basic example or links here. 14 | דוגמה פשוטה או קישורים מתאימים 15 | 16 | ### Motivation | תועלת 17 | Why are we doing this? What use cases does it support? What is the expected outcome? 18 | למה לעשות זאת? מה התועלת? 19 | -------------------------------------------------------------------------------- /lib/file_sync/file_sync_event.dart: -------------------------------------------------------------------------------- 1 | sealed class FileSyncEvent { 2 | const FileSyncEvent(); 3 | } 4 | 5 | class StartSync extends FileSyncEvent { 6 | const StartSync(); 7 | } 8 | 9 | class StopSync extends FileSyncEvent { 10 | const StopSync(); 11 | } 12 | 13 | class UpdateProgress extends FileSyncEvent { 14 | final int current; 15 | final int total; 16 | 17 | const UpdateProgress({ 18 | required this.current, 19 | required this.total, 20 | }); 21 | } 22 | 23 | class ResetState extends FileSyncEvent { 24 | const ResetState(); 25 | } 26 | -------------------------------------------------------------------------------- /lib/personal_notes/personal_notes_system.dart: -------------------------------------------------------------------------------- 1 | export 'bloc/personal_notes_bloc.dart'; 2 | export 'bloc/personal_notes_event.dart'; 3 | export 'bloc/personal_notes_state.dart'; 4 | export 'models/personal_note.dart'; 5 | export 'services/personal_notes_service.dart'; 6 | export 'repository/personal_notes_repository.dart'; 7 | export 'migration/legacy_notes_converter.dart'; 8 | export 'storage/personal_notes_storage.dart'; 9 | export 'utils/note_text_utils.dart'; 10 | export 'view/personal_notes_screen.dart'; 11 | export 'widgets/personal_note_editor_dialog.dart'; 12 | -------------------------------------------------------------------------------- /linux/packaging/deb/make_config.yaml: -------------------------------------------------------------------------------- 1 | display_name: Otzaria 2 | package_name: otzaria 3 | 4 | maintainer: 5 | name: Sivan22 6 | email: sivan.ratson@gmail.com 7 | 8 | priority: optional 9 | 10 | section: x11 11 | 12 | installed_size: 50000 13 | 14 | dependencies: [] 15 | 16 | essential: false 17 | 18 | icon: assets/icon/icon.png 19 | 20 | keywords: 21 | - Hebrew 22 | - Books 23 | - Library 24 | - Jewish 25 | - Text 26 | 27 | generic_name: Hebrew Library 28 | 29 | categories: 30 | - Education 31 | - Utility 32 | 33 | startup_notify: true 34 | -------------------------------------------------------------------------------- /lib/search/view/tantivy_search_field.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:otzaria/search/view/tantivy_full_text_search.dart'; 3 | import 'package:otzaria/search/view/enhanced_search_field.dart'; 4 | 5 | class TantivySearchField extends StatelessWidget { 6 | const TantivySearchField({ 7 | super.key, 8 | required this.widget, 9 | }); 10 | 11 | final TantivyFullTextSearch widget; 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | // נבדוק את השדה החדש 16 | return EnhancedSearchField(widget: widget); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /linux/packaging/rpm/make_config.yaml: -------------------------------------------------------------------------------- 1 | display_name: Otzaria 2 | icon: assets/icon/icon.png 3 | summary: A Hebrew library application for studying Jewish texts 4 | group: Applications/Education 5 | vendor: Sivan22 6 | packager: Sivan22 7 | packagerEmail: sivan.ratson@gmail.com 8 | license: UNLICENSE 9 | url: https://github.com/sivan22/otzaria 10 | 11 | build_arch: x86_64 12 | 13 | requires: [] 14 | 15 | keywords: 16 | - Hebrew 17 | - Books 18 | - Library 19 | - Jewish 20 | - Text 21 | 22 | generic_name: Hebrew Library 23 | 24 | categories: 25 | - Education 26 | - Utility 27 | 28 | startup_notify: true 29 | -------------------------------------------------------------------------------- /packages/shamor_zachor/lib/shamor_zachor.dart: -------------------------------------------------------------------------------- 1 | // Main widget - the only public API 2 | export 'shamor_zachor_widget.dart'; 3 | 4 | // Configuration class for optional initialization 5 | export 'shamor_zachor_config.dart'; 6 | 7 | // New services for dynamic book management 8 | export 'services/dynamic_data_loader_service.dart'; 9 | export 'services/book_scanner_service.dart'; 10 | export 'services/custom_books_service.dart'; 11 | export 'services/shamor_zachor_service_factory.dart'; 12 | 13 | // Models 14 | export 'models/tracked_book_model.dart'; 15 | 16 | // Configuration 17 | export 'config/built_in_books_config.dart'; -------------------------------------------------------------------------------- /lib/text_book/models/search_results.dart: -------------------------------------------------------------------------------- 1 | class TextSearchResult { 2 | final String snippet; 3 | final int index; 4 | final String query; 5 | final String address; 6 | 7 | TextSearchResult({ 8 | required this.snippet, 9 | required this.index, 10 | required this.query, 11 | required this.address, 12 | }); 13 | } 14 | 15 | class BookTextSearchResult extends TextSearchResult { 16 | final String path; 17 | BookTextSearchResult( 18 | {required this.path, 19 | required super.snippet, 20 | required super.index, 21 | required super.query, 22 | required super.address}); 23 | } 24 | -------------------------------------------------------------------------------- /packages/shamor_zachor/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: shamor_zachor 2 | description: שמור וזכור - מערכת מעקב לימוד תורני 3 | version: 1.0.0 4 | 5 | environment: 6 | sdk: ^3.5.4 7 | 8 | dependencies: 9 | flutter: 10 | sdk: flutter 11 | provider: ^6.0.5 12 | shared_preferences: ^2.2.0 13 | path_provider: ^2.1.1 14 | path: ^1.9.0 15 | uuid: ^4.5.1 16 | kosher_dart: ^2.0.18 17 | confetti: ^0.8.0 18 | logging: ^1.3.0 19 | 20 | dev_dependencies: 21 | flutter_test: 22 | sdk: flutter 23 | flutter_lints: ^6.0.0 24 | 25 | dependency_overrides: 26 | lints: ^6.0.0 27 | 28 | flutter: 29 | assets: 30 | - assets/data/ -------------------------------------------------------------------------------- /lib/history/bloc/history_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:otzaria/bookmarks/models/bookmark.dart'; 2 | 3 | abstract class HistoryState { 4 | final List history; 5 | HistoryState(this.history); 6 | } 7 | 8 | class HistoryInitial extends HistoryState { 9 | HistoryInitial() : super([]); 10 | } 11 | 12 | class HistoryLoading extends HistoryState { 13 | HistoryLoading(super.history); 14 | } 15 | 16 | class HistoryLoaded extends HistoryState { 17 | HistoryLoaded(super.history); 18 | } 19 | 20 | class HistoryError extends HistoryState { 21 | final String message; 22 | HistoryError(super.history, this.message); 23 | } 24 | -------------------------------------------------------------------------------- /lib/utils/color_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ColorUtils { 4 | static Color colorFromString(String? colorString) { 5 | if (colorString == null) { 6 | return const Color(0xff2c1b02); // Default color 7 | } 8 | if (colorString.startsWith('#')) { 9 | colorString = colorString.substring(1); 10 | } 11 | if (colorString.length == 6) { 12 | colorString = 'ff$colorString'; 13 | } 14 | return Color(int.parse(colorString, radix: 16)); 15 | } 16 | 17 | static String colorToString(Color color) { 18 | return color.toARGB32().toRadixString(16); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report---.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Bug report \U0001F41E דיווח על באג" 3 | about: Create a bug report | משהו אינו עובד כצפוי 4 | labels: באג 5 | 6 | --- 7 | 8 | ## Describe the bug | תיאור הבאג 9 | A clear and concise description of what the bug is. 10 | תיאור מפורט וברור שמגדיר מהו הבאג 11 | 12 | ### Steps to reproduce | צעדים לשחזור 13 | Steps to reproduce the behavior. 14 | כיצד ניתן לשחזר שוב את הבאג? 15 | 16 | ### Expected behavior | התנהגות צפוייה 17 | A clear and concise description of what you expected to happen. 18 | תיאור ברור של מה שמצופה מהתוכנה לעשות. 19 | 20 | ### Environment | סביבה 21 | - OS | מערכת הפעלה: [e.g. Arch Linux] 22 | - version | גרסת התוכנה: 23 | -------------------------------------------------------------------------------- /lib/bookmarks/repository/bookmark_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:otzaria/bookmarks/models/bookmark.dart'; 2 | import 'package:otzaria/data/repository/base_list_repository.dart'; 3 | 4 | class BookmarkRepository extends BaseListRepository { 5 | BookmarkRepository() 6 | : super( 7 | boxName: 'bookmarks', 8 | key: 'key-bookmarks', 9 | fromJson: (json) => Bookmark.fromJson(json), 10 | toJson: (bookmark) => bookmark.toJson(), 11 | ); 12 | 13 | Future> loadBookmarks() async => load(); 14 | 15 | Future saveBookmarks(List bookmarks) async => save(bookmarks); 16 | 17 | Future clearBookmarks() async => clear(); 18 | } 19 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = otzaria 9 | 10 | MACOSX_DEPLOYMENT_TARGET = 10.15 11 | 12 | 13 | // The application's bundle identifier 14 | PRODUCT_BUNDLE_IDENTIFIER = com.example.otzaria 15 | 16 | // The copyright displayed in application information 17 | PRODUCT_COPYRIGHT = Copyright © 2024 com.example. All rights reserved. 18 | -------------------------------------------------------------------------------- /windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_settings_screens/flutter_settings_screens.dart'; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | import 'package:otzaria/navigation/main_window_screen.dart'; 5 | 6 | void main() { 7 | testWidgets('finds main window', (WidgetTester tester) async { 8 | await Settings.init(); 9 | 10 | // Build our app and trigger a frame. 11 | await tester.pumpWidget(const MaterialApp(home: MainWindowScreen())); 12 | 13 | // Verify that main window is shown 14 | expect(find.byType(MainWindowScreen), findsOneWidget); 15 | 16 | //if the oriention is landscape Verify that the navigation bar is shown 17 | expect(find.byType(Scaffold), findsOneWidget); 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /lib/navigation/navigation_repository.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:flutter_settings_screens/flutter_settings_screens.dart'; 3 | 4 | class NavigationRepository { 5 | bool checkLibraryIsEmpty() { 6 | final libraryPath = Settings.getValue('key-library-path'); 7 | if (libraryPath == null) { 8 | return true; 9 | } 10 | 11 | final libraryDir = Directory('$libraryPath${Platform.pathSeparator}אוצריא'); 12 | if (!libraryDir.existsSync() || libraryDir.listSync().isEmpty) { 13 | return true; 14 | } 15 | 16 | return false; 17 | } 18 | 19 | Future refreshLibrary() async { 20 | // This will be implemented when we migrate the library bloc 21 | // For now, it's a placeholder for the refresh functionality 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | com.apple.security.files.user-selected.read-write 10 | 11 | com.apple.security.files.downloads.read-write 12 | 13 | com.apple.security.temporary-exception.mach-lookup.global-name 14 | 15 | com.apple.mail 16 | 17 | com.apple.security.temporary-exception.apple-events 18 | 19 | com.apple.mail 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /lib/text_book/editing/helpers/editor_settings_helper.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_settings_screens/flutter_settings_screens.dart'; 2 | import '../models/editor_settings.dart'; 3 | 4 | /// Helper class to get editor settings from SharedPreferences 5 | class EditorSettingsHelper { 6 | static EditorSettings getSettings() { 7 | return EditorSettings( 8 | previewDebounce: Duration( 9 | milliseconds: 10 | Settings.getValue('key-editor-preview-debounce')?.toInt() ?? 11 | 150, 12 | ), 13 | globalDraftsQuotaMB: 14 | Settings.getValue('key-editor-drafts-quota')?.toInt() ?? 100, 15 | draftCleanupDays: 16 | Settings.getValue('key-editor-draft-cleanup-days')?.toInt() ?? 17 | 30, 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/utils/settings_wrapper.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_settings_screens/flutter_settings_screens.dart'; 2 | 3 | /// A wrapper around the static Settings class to make it more testable. 4 | /// This allows us to mock the Settings class in tests. 5 | class SettingsWrapper { 6 | /// Gets a value from settings with a default value if not found. 7 | T getValue(String key, {required T defaultValue}) { 8 | return Settings.getValue(key, defaultValue: defaultValue) ?? 9 | defaultValue; 10 | } 11 | 12 | /// Sets a value in settings. 13 | Future setValue(String key, T value) { 14 | return Settings.setValue(key, value); 15 | } 16 | 17 | /// Removes a value from settings. 18 | Future remove(String key) { 19 | return Settings.setValue(key, null); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/shamor_zachor/lib/shamor_zachor_config.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// Configuration class for ShamorZachor widget 4 | class ShamorZachorConfig { 5 | /// Optional asset path override for data files 6 | final String? assetsBasePath; 7 | 8 | /// Optional theme data to inherit from host app 9 | final ThemeData? themeData; 10 | 11 | /// Optional text direction override 12 | final TextDirection? textDirection; 13 | 14 | /// Optional locale override 15 | final Locale? locale; 16 | 17 | const ShamorZachorConfig({ 18 | this.assetsBasePath, 19 | this.themeData, 20 | this.textDirection, 21 | this.locale, 22 | }); 23 | 24 | /// Default configuration with sensible defaults 25 | static const ShamorZachorConfig defaultConfig = ShamorZachorConfig(); 26 | } -------------------------------------------------------------------------------- /lib/history/bloc/history_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:otzaria/bookmarks/models/bookmark.dart'; 2 | import 'package:otzaria/tabs/models/tab.dart'; 3 | 4 | abstract class HistoryEvent {} 5 | 6 | class LoadHistory extends HistoryEvent {} 7 | 8 | class AddHistory extends HistoryEvent { 9 | final OpenedTab tab; 10 | AddHistory(this.tab); 11 | } 12 | 13 | class CaptureStateForHistory extends HistoryEvent { 14 | final OpenedTab tab; 15 | CaptureStateForHistory(this.tab); 16 | } 17 | 18 | class FlushHistory extends HistoryEvent {} 19 | 20 | class BulkAddHistory extends HistoryEvent { 21 | final List snapshots; 22 | BulkAddHistory(this.snapshots); 23 | } 24 | 25 | class RemoveHistory extends HistoryEvent { 26 | final int index; 27 | RemoveHistory(this.index); 28 | } 29 | 30 | class ClearHistory extends HistoryEvent {} 31 | -------------------------------------------------------------------------------- /lib/find_ref/find_ref_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:otzaria/models/books.dart'; // Import Book models 2 | import 'package:equatable/equatable.dart'; 3 | 4 | abstract class FindRefEvent extends Equatable { 5 | const FindRefEvent(); 6 | 7 | @override 8 | List get props => []; 9 | } 10 | 11 | class SearchRefRequested extends FindRefEvent { 12 | final String refText; 13 | const SearchRefRequested(this.refText); 14 | 15 | @override 16 | List get props => [refText]; 17 | } 18 | 19 | class ClearSearchRequested extends FindRefEvent {} 20 | 21 | class OpenBookRequested extends FindRefEvent { 22 | final Book book; 23 | final int index; 24 | 25 | const OpenBookRequested({ 26 | required this.book, 27 | required this.index, 28 | }); 29 | 30 | @override 31 | List get props => [book, index]; 32 | } 33 | -------------------------------------------------------------------------------- /assets/icon/memorial_candle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /lib/app_bloc_observer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_bloc/flutter_bloc.dart'; 2 | 3 | class AppBlocObserver extends BlocObserver { 4 | @override 5 | void onEvent(Bloc bloc, Object? event) { 6 | super.onEvent(bloc, event); 7 | print('🔵 ${bloc.runtimeType} Event: $event'); 8 | } 9 | 10 | @override 11 | void onChange(BlocBase bloc, Change change) { 12 | super.onChange(bloc, change); 13 | print('🟡 ${bloc.runtimeType} Change: $change'); 14 | } 15 | 16 | @override 17 | void onError(BlocBase bloc, Object error, StackTrace stackTrace) { 18 | print('🔴 ${bloc.runtimeType} Error: $error\n$stackTrace'); 19 | super.onError(bloc, error, stackTrace); 20 | } 21 | 22 | @override 23 | void onTransition(Bloc bloc, Transition transition) { 24 | super.onTransition(bloc, transition); 25 | print('🟢 ${bloc.runtimeType} Transition: $transition'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/daf_yomi/calendar.dart: -------------------------------------------------------------------------------- 1 | import 'package:kosher_dart/kosher_dart.dart'; 2 | 3 | Daf getDafYomi(DateTime date) { 4 | JewishCalendar jewishCalendar = JewishCalendar.fromDateTime(date); 5 | return YomiCalculator.getDafYomiBavli(jewishCalendar); 6 | } 7 | 8 | String getHebrewDateFormattedAsString(DateTime dateTime) { 9 | final hebrewCalendar = JewishCalendar.fromDateTime(dateTime); 10 | HebrewDateFormatter hebrewDateFormatter = HebrewDateFormatter() 11 | ..hebrewFormat = true; 12 | return hebrewDateFormatter.format(hebrewCalendar); 13 | } 14 | 15 | String getHebrewTimeStamp() { 16 | return '${getHebrewDateFormattedAsString(DateTime.now())} ${DateTime.now().hour}:${DateTime.now().minute}:${DateTime.now().second}'; 17 | } 18 | 19 | String formatAmud(int amud) { 20 | return HebrewDateFormatter() 21 | .formatHebrewNumber(amud) 22 | .replaceAll('״', '') 23 | .replaceAll('׳', ''); 24 | } 25 | -------------------------------------------------------------------------------- /lib/navigation/bloc/navigation_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:otzaria/navigation/bloc/navigation_state.dart'; 3 | 4 | abstract class NavigationEvent extends Equatable { 5 | const NavigationEvent(); 6 | 7 | @override 8 | List get props => []; 9 | } 10 | 11 | class NavigateToScreen extends NavigationEvent { 12 | final Screen screen; 13 | 14 | const NavigateToScreen(this.screen); 15 | 16 | @override 17 | List get props => [screen]; 18 | } 19 | 20 | class CheckLibrary extends NavigationEvent { 21 | const CheckLibrary(); 22 | } 23 | 24 | class OpenNewSearchTab extends NavigationEvent { 25 | const OpenNewSearchTab(); 26 | } 27 | 28 | class UpdateLibraryStatus extends NavigationEvent { 29 | final bool isEmpty; 30 | 31 | const UpdateLibraryStatus(this.isEmpty); 32 | 33 | @override 34 | List get props => [isEmpty]; 35 | } 36 | -------------------------------------------------------------------------------- /macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.network.server 10 | 11 | com.apple.security.network.client 12 | 13 | com.apple.security.files.user-selected.read-write 14 | 15 | com.apple.security.files.downloads.read-write 16 | 17 | com.apple.security.temporary-exception.mach-lookup.global-name 18 | 19 | com.apple.mail 20 | 21 | com.apple.security.temporary-exception.apple-events 22 | 23 | com.apple.mail 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/indexing/bloc/indexing_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:otzaria/library/models/library.dart'; 3 | 4 | abstract class IndexingEvent extends Equatable { 5 | const IndexingEvent(); 6 | 7 | @override 8 | List get props => []; 9 | } 10 | 11 | class StartIndexing extends IndexingEvent { 12 | final Library library; 13 | 14 | const StartIndexing(this.library); 15 | 16 | @override 17 | List get props => [library]; 18 | } 19 | 20 | class ClearIndex extends IndexingEvent {} 21 | 22 | class CancelIndexing extends IndexingEvent {} 23 | 24 | class UpdateIndexingProgress extends IndexingEvent { 25 | final int processed; 26 | final int total; 27 | 28 | const UpdateIndexingProgress({ 29 | required this.processed, 30 | required this.total, 31 | }); 32 | 33 | @override 34 | List get props => [ 35 | processed, 36 | total, 37 | ]; 38 | } 39 | -------------------------------------------------------------------------------- /lib/tabs/bloc/tabs_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:otzaria/tabs/models/tab.dart'; 3 | 4 | class TabsState extends Equatable { 5 | final List tabs; 6 | final int currentTabIndex; 7 | 8 | const TabsState({ 9 | required this.tabs, 10 | required this.currentTabIndex, 11 | }); 12 | 13 | factory TabsState.initial() { 14 | return const TabsState( 15 | tabs: [], 16 | currentTabIndex: 0, 17 | ); 18 | } 19 | 20 | TabsState copyWith({ 21 | List? tabs, 22 | int? currentTabIndex, 23 | }) { 24 | return TabsState( 25 | tabs: tabs ?? this.tabs, 26 | currentTabIndex: currentTabIndex ?? this.currentTabIndex, 27 | ); 28 | } 29 | 30 | bool get hasOpenTabs => tabs.isNotEmpty; 31 | OpenedTab? get currentTab => hasOpenTabs ? tabs[currentTabIndex] : null; 32 | 33 | @override 34 | List get props => [tabs, currentTabIndex]; 35 | } 36 | -------------------------------------------------------------------------------- /lib/text_book/models/commentator_group.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | class CommentatorGroup extends Equatable { 4 | final String title; 5 | final List commentators; 6 | 7 | const CommentatorGroup({ 8 | required this.title, 9 | required this.commentators, 10 | }); 11 | 12 | CommentatorGroup copyWith({ 13 | String? title, 14 | List? commentators, 15 | }) { 16 | return CommentatorGroup( 17 | title: title ?? this.title, 18 | commentators: commentators ?? this.commentators, 19 | ); 20 | } 21 | 22 | @override 23 | List get props => [title, commentators]; 24 | 25 | /// Helper method to find commentator group by title 26 | static CommentatorGroup groupByTitle( 27 | List groups, String title) => 28 | groups.firstWhere((g) => g.title == title, 29 | orElse: () => const CommentatorGroup(title: '', commentators: [])); 30 | } 31 | -------------------------------------------------------------------------------- /windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /lib/data/repository/base_list_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:otzaria/data/repository/hive_list_repository.dart'; 2 | 3 | /// Base class for repositories that use HiveListRepository with common operations 4 | abstract class BaseListRepository { 5 | final HiveListRepository _repo; 6 | 7 | BaseListRepository({ 8 | required String boxName, 9 | required String key, 10 | required T Function(Map) fromJson, 11 | required Map Function(T) toJson, 12 | }) : _repo = HiveListRepository( 13 | boxName: boxName, 14 | key: key, 15 | fromJson: fromJson, 16 | toJson: toJson, 17 | ); 18 | 19 | Future> load() async => _repo.load(); 20 | Future save(List items) async => _repo.save(items); 21 | Future clear() async => _repo.clear(); 22 | Future addItem(T item) async => _repo.addItem(item); 23 | Future removeAt(int index) async => _repo.removeAt(index); 24 | } 25 | -------------------------------------------------------------------------------- /lib/navigation/bloc/navigation_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | enum Screen { library, find, reading, search, more, settings, about } 4 | 5 | class NavigationState extends Equatable { 6 | final Screen currentScreen; 7 | final bool isLibraryEmpty; 8 | 9 | const NavigationState({ 10 | required this.currentScreen, 11 | this.isLibraryEmpty = false, 12 | }); 13 | 14 | factory NavigationState.initial(bool hasTabs) { 15 | return NavigationState( 16 | currentScreen: hasTabs ? Screen.reading : Screen.library, 17 | isLibraryEmpty: false, 18 | ); 19 | } 20 | 21 | NavigationState copyWith({ 22 | Screen? currentScreen, 23 | bool? isLibraryEmpty, 24 | }) { 25 | return NavigationState( 26 | currentScreen: currentScreen ?? this.currentScreen, 27 | isLibraryEmpty: isLibraryEmpty ?? this.isLibraryEmpty, 28 | ); 29 | } 30 | 31 | @override 32 | List get props => [currentScreen, isLibraryEmpty]; 33 | } 34 | -------------------------------------------------------------------------------- /packages/shamor_zachor/test/category_aliases_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:shamor_zachor/utils/category_aliases.dart'; 3 | 4 | void main() { 5 | group('CategoryAliases', () { 6 | test('normalize maps legacy names to new display names', () { 7 | expect(CategoryAliases.normalize('תנך'), 'תנ"ך'); 8 | expect(CategoryAliases.normalize('ש"ס'), 'תלמוד בבלי'); 9 | expect(CategoryAliases.normalize('ירושלמי'), 'תלמוד ירושלמי'); 10 | // unchanged 11 | expect(CategoryAliases.normalize('משנה'), 'משנה'); 12 | }); 13 | 14 | test('legacyAliasesForNew returns the old keys', () { 15 | expect(CategoryAliases.legacyAliasesForNew('תנ"ך'), contains('תנך')); 16 | expect(CategoryAliases.legacyAliasesForNew('תלמוד בבלי'), contains('ש"ס')); 17 | expect(CategoryAliases.legacyAliasesForNew('תלמוד ירושלמי'), contains('ירושלמי')); 18 | expect(CategoryAliases.legacyAliasesForNew('משנה'), isEmpty); 19 | }); 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /lib/empty_library/bloc/empty_library_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | abstract class EmptyLibraryEvent extends Equatable { 4 | @override 5 | List get props => []; 6 | } 7 | 8 | class DownloadLibraryRequested extends EmptyLibraryEvent {} 9 | 10 | class PickDirectoryRequested extends EmptyLibraryEvent {} 11 | 12 | class PickAndExtractZipRequested extends EmptyLibraryEvent {} 13 | 14 | class CancelDownloadRequested extends EmptyLibraryEvent {} 15 | 16 | class DownloadProgressUpdated extends EmptyLibraryEvent { 17 | final double progress; 18 | final double downloadedMB; 19 | final double downloadSpeed; 20 | final String currentOperation; 21 | 22 | DownloadProgressUpdated({ 23 | required this.progress, 24 | required this.downloadedMB, 25 | required this.downloadSpeed, 26 | required this.currentOperation, 27 | }); 28 | 29 | @override 30 | List get props => 31 | [progress, downloadedMB, downloadSpeed, currentOperation]; 32 | } 33 | -------------------------------------------------------------------------------- /packages/shamor_zachor/lib/utils/category_aliases.dart: -------------------------------------------------------------------------------- 1 | /// Utility for mapping legacy category names to their new display names 2 | /// and resolving aliases during cache migration. 3 | class CategoryAliases { 4 | static const Map oldToNew = { 5 | 'תנך': 'תנ"ך', 6 | 'ש"ס': 'תלמוד בבלי', 7 | 'ירושלמי': 'תלמוד ירושלמי', 8 | }; 9 | 10 | /// Return the normalized (new) name for a category, or the original 11 | /// if it is already using the new naming. 12 | static String normalize(String categoryName) { 13 | return oldToNew[categoryName] ?? categoryName; 14 | } 15 | 16 | /// Return legacy aliases for a given new category name. 17 | static List legacyAliasesForNew(String newName) { 18 | switch (newName) { 19 | case 'תנ"ך': 20 | return const ['תנך']; 21 | case 'תלמוד בבלי': 22 | return const ['ש"ס']; 23 | case 'תלמוד ירושלמי': 24 | return const ['ירושלמי']; 25 | default: 26 | return const []; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | } 9 | settings.ext.flutterSdkPath = flutterSdkPath() 10 | 11 | includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") 12 | 13 | repositories { 14 | google() 15 | mavenCentral() 16 | gradlePluginPortal() 17 | } 18 | 19 | plugins { 20 | id "dev.flutter.flutter-gradle-plugin" version "2.0.0" apply false 21 | } 22 | } 23 | 24 | plugins { 25 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 26 | id("com.android.application") version "8.7.0" apply false 27 | id("org.jetbrains.kotlin.android") version "1.9.25" apply false 28 | } 29 | 30 | include ":app" 31 | -------------------------------------------------------------------------------- /packages/shamor_zachor/test/shamor_zachor_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:shamor_zachor/shamor_zachor.dart'; 3 | 4 | void main() { 5 | group('ShamorZachror Package Tests', () { 6 | test('ShamorZachorWidget should be instantiable', () { 7 | const widget = ShamorZachorWidget(); 8 | expect(widget, isNotNull); 9 | expect(widget.config, equals(ShamorZachorConfig.defaultConfig)); 10 | }); 11 | 12 | test('ShamorZachorConfig should have default values', () { 13 | const config = ShamorZachorConfig.defaultConfig; 14 | expect(config.assetsBasePath, isNull); 15 | expect(config.themeData, isNull); 16 | expect(config.textDirection, isNull); 17 | expect(config.locale, isNull); 18 | }); 19 | 20 | test('ShamorZachorConfig should accept custom values', () { 21 | const config = ShamorZachorConfig( 22 | assetsBasePath: 'custom/path', 23 | ); 24 | expect(config.assetsBasePath, equals('custom/path')); 25 | }); 26 | }); 27 | } -------------------------------------------------------------------------------- /lib/indexing/bloc/indexing_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | abstract class IndexingState extends Equatable { 4 | final int? booksProcessed; 5 | final int? totalBooks; 6 | final List? booksDone; 7 | 8 | const IndexingState({this.booksProcessed, this.totalBooks, this.booksDone}); 9 | 10 | @override 11 | List get props => [booksProcessed, totalBooks, booksDone]; 12 | } 13 | 14 | class IndexingInitial extends IndexingState {} 15 | 16 | class IndexingInProgress extends IndexingState { 17 | const IndexingInProgress( 18 | {super.booksProcessed, super.totalBooks, super.booksDone}); 19 | } 20 | 21 | class IndexingComplete extends IndexingState { 22 | const IndexingComplete(); 23 | } 24 | 25 | class IndexingError extends IndexingState { 26 | final String error; 27 | 28 | const IndexingError(this.error, 29 | {super.booksProcessed, super.totalBooks, super.booksDone}); 30 | 31 | @override 32 | List get props => [error, booksProcessed, totalBooks, booksDone]; 33 | } 34 | -------------------------------------------------------------------------------- /lib/workspaces/workspace.dart: -------------------------------------------------------------------------------- 1 | import 'package:otzaria/tabs/models/tab.dart'; 2 | 3 | /// Represents a workspace in the application. 4 | /// 5 | /// A `Workspace` object has a [name] which is the name of the workspace, 6 | /// a [bookmarks] list which is a list of bookmarks in the workspace, 7 | /// and a [currentTab] which is the index of the current tab. 8 | class Workspace { 9 | String name; 10 | final List tabs; 11 | int currentTab; 12 | 13 | Workspace({required this.name, required this.tabs, this.currentTab = 0}); 14 | 15 | factory Workspace.fromJson(Map json) { 16 | return Workspace( 17 | name: json['name'], 18 | tabs: List.from( 19 | json['tabs'].map((tab) => OpenedTab.fromJson(tab)), 20 | ), 21 | currentTab: json['currentTab']); 22 | } 23 | 24 | Map toJson() { 25 | return { 26 | 'name': name, 27 | 'tabs': tabs.map((tab) => tab.toJson()).toList(), 28 | 'currentTab': currentTab 29 | }; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.9.25' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 10 | } 11 | } 12 | 13 | allprojects { 14 | repositories { 15 | google() 16 | mavenCentral() 17 | } 18 | } 19 | 20 | rootProject.buildDir = '../build' 21 | subprojects { 22 | project.buildDir = "${rootProject.buildDir}/${project.name}" 23 | } 24 | subprojects { 25 | afterEvaluate { project -> 26 | if (project.plugins.hasPlugin("com.android.application") || 27 | project.plugins.hasPlugin("com.android.library")) { 28 | project.android { 29 | compileSdkVersion 35 30 | buildToolsVersion "35.0.0" 31 | } 32 | } 33 | } 34 | } 35 | subprojects { 36 | project.evaluationDependsOn(':app') 37 | } 38 | 39 | tasks.register("clean", Delete) { 40 | delete rootProject.buildDir 41 | } 42 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "otzaria", 3 | "short_name": "otzaria", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "abb292a07e20d696c4568099f918f6c5f330e6b0" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: abb292a07e20d696c4568099f918f6c5f330e6b0 17 | base_revision: abb292a07e20d696c4568099f918f6c5f330e6b0 18 | - platform: android 19 | create_revision: abb292a07e20d696c4568099f918f6c5f330e6b0 20 | base_revision: abb292a07e20d696c4568099f918f6c5f330e6b0 21 | 22 | # User provided section 23 | 24 | # List of Local paths (relative to this file) that should be 25 | # ignored by the migrate tool. 26 | # 27 | # Files that are not part of the templates will be ignored by default. 28 | unmanaged_files: 29 | - 'lib/main.dart' 30 | - 'ios/Runner.xcodeproj/project.pbxproj' 31 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "win32_window.h" 10 | 11 | // A window that does nothing but host a Flutter view. 12 | class FlutterWindow : public Win32Window { 13 | public: 14 | // Creates a new FlutterWindow hosting a Flutter view running |project|. 15 | explicit FlutterWindow(const flutter::DartProject& project); 16 | virtual ~FlutterWindow(); 17 | 18 | protected: 19 | // Win32Window: 20 | bool OnCreate() override; 21 | void OnDestroy() override; 22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 23 | LPARAM const lparam) noexcept override; 24 | 25 | private: 26 | // The project to run. 27 | flutter::DartProject project_; 28 | 29 | // The Flutter instance hosted by this window. 30 | std::unique_ptr flutter_controller_; 31 | }; 32 | 33 | #endif // RUNNER_FLUTTER_WINDOW_H_ 34 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | irondash_engine_context 7 | isar_flutter_libs 8 | printing 9 | screen_retriever 10 | sqlite3_flutter_libs 11 | super_native_extensions 12 | url_launcher_linux 13 | window_manager 14 | ) 15 | 16 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 17 | pdfrx 18 | search_engine 19 | ) 20 | 21 | set(PLUGIN_BUNDLED_LIBRARIES) 22 | 23 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 24 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 25 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 26 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 27 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 28 | endforeach(plugin) 29 | 30 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 31 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 32 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 33 | endforeach(ffi_plugin) 34 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | irondash_engine_context 7 | isar_flutter_libs 8 | printing 9 | screen_retriever 10 | sqlite3_flutter_libs 11 | super_native_extensions 12 | url_launcher_windows 13 | window_manager 14 | ) 15 | 16 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 17 | pdfrx 18 | search_engine 19 | ) 20 | 21 | set(PLUGIN_BUNDLED_LIBRARIES) 22 | 23 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 24 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 25 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 26 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 27 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 28 | endforeach(plugin) 29 | 30 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 31 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 32 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 33 | endforeach(ffi_plugin) 34 | -------------------------------------------------------------------------------- /lib/widgets/password_dialog.dart: -------------------------------------------------------------------------------- 1 | // 2 | // Simple password dialog 3 | // 4 | import 'package:flutter/material.dart'; 5 | 6 | Future passwordDialog(BuildContext context) async { 7 | final textController = TextEditingController(); 8 | return await showDialog( 9 | context: context, 10 | barrierDismissible: false, 11 | builder: (context) { 12 | return AlertDialog( 13 | title: const Text('Enter password'), 14 | content: TextField( 15 | controller: textController, 16 | autofocus: true, 17 | keyboardType: TextInputType.visiblePassword, 18 | obscureText: true, 19 | onSubmitted: (value) => Navigator.of(context).pop(value), 20 | ), 21 | actions: [ 22 | TextButton( 23 | onPressed: () => Navigator.of(context).pop(null), 24 | child: const Text('Cancel'), 25 | ), 26 | TextButton( 27 | onPressed: () => Navigator.of(context).pop(textController.text), 28 | child: const Text('OK'), 29 | ), 30 | ], 31 | ); 32 | }, 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /lib/tabs/tabs_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:hive/hive.dart'; 2 | import 'package:otzaria/tabs/models/tab.dart'; 3 | import 'package:flutter/foundation.dart'; 4 | 5 | class TabsRepository { 6 | static const String _tabsBoxKey = 'key-tabs'; 7 | static const String _currentTabKey = 'key-current-tab'; 8 | 9 | List loadTabs() { 10 | try { 11 | final box = Hive.box(name: 'tabs'); 12 | final rawTabs = box.get(_tabsBoxKey, defaultValue: []) as List; 13 | return List.from( 14 | rawTabs.map((e) => OpenedTab.fromJson(e)).toList(), 15 | ); 16 | } catch (e) { 17 | debugPrint('Error loading tabs from disk: $e'); 18 | Hive.box(name: 'tabs').put(_tabsBoxKey, []); 19 | return []; 20 | } 21 | } 22 | 23 | int loadCurrentTabIndex() { 24 | return Hive.box(name: 'tabs').get(_currentTabKey, defaultValue: 0); 25 | } 26 | 27 | void saveTabs(List tabs, int currentTabIndex) { 28 | final box = Hive.box(name: 'tabs'); 29 | box.put(_tabsBoxKey, tabs.map((tab) => tab.toJson()).toList()); 30 | box.put(_currentTabKey, currentTabIndex); 31 | } 32 | } -------------------------------------------------------------------------------- /lib/file_sync/file_sync_state.dart: -------------------------------------------------------------------------------- 1 | enum FileSyncStatus { initial, syncing, completed, error } 2 | 3 | class FileSyncState { 4 | final FileSyncStatus status; 5 | final int currentProgress; 6 | final int totalFiles; 7 | final String message; 8 | final bool hasNewSync; 9 | final String? errorMessage; 10 | 11 | const FileSyncState({ 12 | this.status = FileSyncStatus.initial, 13 | this.currentProgress = 0, 14 | this.totalFiles = 0, 15 | this.message = 'לחץ לסנכרון', 16 | this.hasNewSync = false, 17 | this.errorMessage, 18 | }); 19 | 20 | FileSyncState copyWith({ 21 | FileSyncStatus? status, 22 | int? currentProgress, 23 | int? totalFiles, 24 | String? message, 25 | bool? hasNewSync, 26 | String? errorMessage, 27 | }) { 28 | return FileSyncState( 29 | status: status ?? this.status, 30 | currentProgress: currentProgress ?? this.currentProgress, 31 | totalFiles: totalFiles ?? this.totalFiles, 32 | message: message ?? this.message, 33 | hasNewSync: hasNewSync ?? this.hasNewSync, 34 | errorMessage: errorMessage ?? this.errorMessage, 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/unit/mocks/mock_settings.mocks.dart: -------------------------------------------------------------------------------- 1 | // Mocks generated by Mockito 5.4.4 from annotations 2 | // in otzaria/test/unit/mocks/mock_settings.dart. 3 | // Do not manually edit this file. 4 | 5 | // ignore_for_file: no_leading_underscores_for_library_prefixes 6 | import 'package:flutter_settings_screens/src/settings.dart' as _i2; 7 | import 'package:mockito/mockito.dart' as _i1; 8 | 9 | // ignore_for_file: type=lint 10 | // ignore_for_file: avoid_redundant_argument_values 11 | // ignore_for_file: avoid_setters_without_getters 12 | // ignore_for_file: comment_references 13 | // ignore_for_file: deprecated_member_use 14 | // ignore_for_file: deprecated_member_use_from_same_package 15 | // ignore_for_file: implementation_imports 16 | // ignore_for_file: invalid_use_of_visible_for_testing_member 17 | // ignore_for_file: prefer_const_constructors 18 | // ignore_for_file: unnecessary_parenthesis 19 | // ignore_for_file: camel_case_types 20 | // ignore_for_file: subtype_of_sealed_class 21 | 22 | /// A class which mocks [Settings]. 23 | /// 24 | /// See the documentation for Mockito's code generation for more information. 25 | class MockSettings extends _i1.Mock implements _i2.Settings {} 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .build/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | .swiftpm/ 13 | migrate_working_dir/ 14 | 15 | # IntelliJ related 16 | *.iml 17 | *.ipr 18 | *.iws 19 | .idea/ 20 | 21 | # The .vscode folder contains launch configuration and tasks you configure in 22 | # VS Code which you may wish to be included in version control, so this line 23 | # is commented out by default. 24 | #.vscode/ 25 | 26 | # Flutter/Dart/Pub related 27 | **/doc/api/ 28 | **/ios/Flutter/.last_build_id 29 | .dart_tool/ 30 | .flutter-plugins 31 | .flutter-plugins-dependencies 32 | .pub-cache/ 33 | .pub/ 34 | build/ 35 | windows 36 | macos/ 37 | ios/ 38 | .dart_tool 39 | web/ 40 | .idea/ 41 | 42 | 43 | 44 | 45 | # Symbolication related 46 | app.*.symbols 47 | 48 | # Obfuscation related 49 | app.*.map.json 50 | 51 | # Android Studio will place build artifacts here 52 | /android/app/debug 53 | /android/app/profile 54 | /android/app/release 55 | 56 | .git/ 57 | .vscode/ 58 | android/ 59 | build/ 60 | fonts/ 61 | 62 | installer/otzaria-0.9.71-windows.exe 63 | installer/otzaria-0.9.71-windows-full.exe 64 | pubspec.lock 65 | flutter/ 66 | -------------------------------------------------------------------------------- /lib/workspaces/bloc/workspace_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:otzaria/workspaces/workspace.dart'; 3 | 4 | class WorkspaceState extends Equatable { 5 | final List workspaces; 6 | final bool isLoading; 7 | final String? error; 8 | final int? currentWorkspace; 9 | 10 | const WorkspaceState( 11 | {required this.workspaces, 12 | this.isLoading = false, 13 | this.error, 14 | this.currentWorkspace}); 15 | 16 | factory WorkspaceState.initial() { 17 | return const WorkspaceState( 18 | workspaces: [], 19 | isLoading: true, 20 | error: null, 21 | currentWorkspace: null, 22 | ); 23 | } 24 | 25 | WorkspaceState copyWith({ 26 | List? workspaces, 27 | bool? isLoading, 28 | String? error, 29 | int? currentWorkspace, 30 | }) { 31 | return WorkspaceState( 32 | workspaces: workspaces ?? this.workspaces, 33 | isLoading: isLoading ?? this.isLoading, 34 | error: error, 35 | currentWorkspace: currentWorkspace ?? this.currentWorkspace, 36 | ); 37 | } 38 | 39 | @override 40 | List get props => [workspaces, isLoading, error, currentWorkspace]; 41 | } 42 | -------------------------------------------------------------------------------- /.github/workflows/claude.yml: -------------------------------------------------------------------------------- 1 | name: Claude PR Assistant 2 | 3 | on: 4 | issue_comment: 5 | types: [created] 6 | pull_request_review_comment: 7 | types: [created] 8 | issues: 9 | types: [opened, assigned] 10 | pull_request_review: 11 | types: [submitted] 12 | 13 | jobs: 14 | claude-code-action: 15 | if: | 16 | (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || 17 | (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || 18 | (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || 19 | (github.event_name == 'issues' && contains(github.event.issue.body, '@claude')) 20 | runs-on: ubuntu-latest 21 | permissions: 22 | contents: read 23 | pull-requests: read 24 | issues: read 25 | id-token: write 26 | steps: 27 | - name: Checkout repository 28 | uses: actions/checkout@v4 29 | with: 30 | fetch-depth: 1 31 | 32 | - name: Run Claude PR Action 33 | uses: anthropics/claude-code-action@beta 34 | with: 35 | anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} 36 | timeout_minutes: "60" 37 | -------------------------------------------------------------------------------- /packages/shamor_zachor/lib/utils/json_utils.dart: -------------------------------------------------------------------------------- 1 | /// Utilities for safe extraction of typed values from JSON-like structures. 2 | class JsonUtils { 3 | /// Safely read a string value; returns empty string on null or mismatched type. 4 | static String asString(dynamic value) => value is String ? value : ''; 5 | 6 | /// Safely read an integer; accepts numeric strings as well. 7 | static int asInt(dynamic value) { 8 | if (value is int) return value; 9 | if (value is String) { 10 | return int.tryParse(value) ?? 0; 11 | } 12 | if (value is num) { 13 | return value.toInt(); 14 | } 15 | return 0; 16 | } 17 | 18 | /// Safely read a numeric value; accepts numeric strings as well. 19 | static num asNum(dynamic value) { 20 | if (value is num) return value; 21 | if (value is String) { 22 | return num.tryParse(value) ?? 0; 23 | } 24 | return 0; 25 | } 26 | 27 | /// Safely convert a dynamic map into a string-keyed map. 28 | static Map asMap(dynamic value) { 29 | if (value is Map) { 30 | return value; 31 | } 32 | if (value is Map) { 33 | return Map.from(value); 34 | } 35 | return {}; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /sivan22.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDRDCCAiygAwIBAgIUH1H5aMy06UAWBIfw5BJeM4sZj2IwDQYJKoZIhvcNAQEL 3 | BQAwMTELMAkGA1UEBhMCSUwxEDAOBgNVBAoMB3NpdmFuMjIxEDAOBgNVBAMMB3Np 4 | dmFuMjIwHhcNMjUwNTIwMTc1ODIxWhcNMjYwNTIwMTc1ODIxWjAxMQswCQYDVQQG 5 | EwJJTDEQMA4GA1UECgwHc2l2YW4yMjEQMA4GA1UEAwwHc2l2YW4yMjCCASIwDQYJ 6 | KoZIhvcNAQEBBQADggEPADCCAQoCggEBAORb9xbnYKBXN3DHE6upY94McAz+0jNI 7 | /kZeVGDBXOd7k7b1yWnwv3qnw9fuTD+rv+wTApnRWC0UnD11w3v9KL3w4nyuSZtl 8 | PIRW3Q/UgYCNJflqByTDAqjsEQxywevfEw/+r8D6wcioIbEatOEtZgh/QpmKytUo 9 | 1+7ck2m8F0fdEtyUc1wx8dUNoUP3YElTtk3vGirtL7GDpKyaDymPYPKLK8caUrnG 10 | WF0QaKdvoMfqOpyb1DnMM5XTQEgRz4HgkVd0+dBXBfaqWokY+VBhbQqxtrHfysxd 11 | V4oaWBV50w87hAf68R/7ruK0JQrj8NTbbTNjHCkEfmfA+CBwAQPQsBkCAwEAAaNU 12 | MFIwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMAwGA1UdEwEB 13 | /wQCMAAwHQYDVR0OBBYEFPnWeng9JpbQwMkfn9ScuOPloMVAMA0GCSqGSIb3DQEB 14 | CwUAA4IBAQDD/+rr7AIFKOaiD2cD0OcAeeW+47Sgb3+Dv/xDQK4ce704i9jID1x4 15 | QRK3TON13SYOJ6tqZwd4eYljJg64FuX1Ez0855FTJFVqcbPBoy6lf/AIlqciVdkY 16 | 7/snzGnxFY18mSdK8vHyIV8k4hzZBMTmsO5F70W2U5KU6zoJJonVEDhtF5pHkeWA 17 | 4IyUGU/HwhmjZF7c9KsybTN4z1yCocjkAATdhuGfHgPGxCQG7Bv2+P6uwu/Bc3Ky 18 | hykrngDfPhlqRhaQX0vx08xjTzd2MHE4YvSyhjDxhAFfvXhM5tiUDm5jR4Yp21hf 19 | cq8pfjHwKqPhhtEp6q3E3MrkFdlNdp28 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /lib/text_book/editing/models/editor_settings.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | /// Configuration settings for the text editor 4 | class EditorSettings extends Equatable { 5 | /// Debounce duration for preview updates 6 | final Duration previewDebounce; 7 | 8 | /// Global quota for all drafts in MB 9 | final int globalDraftsQuotaMB; 10 | 11 | /// Number of days after which to clean up old drafts 12 | final int draftCleanupDays; 13 | 14 | const EditorSettings({ 15 | this.previewDebounce = const Duration(milliseconds: 150), 16 | this.globalDraftsQuotaMB = 100, 17 | this.draftCleanupDays = 30, 18 | }); 19 | 20 | /// Creates a copy with updated values 21 | EditorSettings copyWith({ 22 | Duration? previewDebounce, 23 | int? globalDraftsQuotaMB, 24 | int? draftCleanupDays, 25 | }) { 26 | return EditorSettings( 27 | previewDebounce: previewDebounce ?? this.previewDebounce, 28 | globalDraftsQuotaMB: globalDraftsQuotaMB ?? this.globalDraftsQuotaMB, 29 | draftCleanupDays: draftCleanupDays ?? this.draftCleanupDays, 30 | ); 31 | } 32 | 33 | @override 34 | List get props => [ 35 | previewDebounce, 36 | globalDraftsQuotaMB, 37 | draftCleanupDays, 38 | ]; 39 | } 40 | -------------------------------------------------------------------------------- /macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | $(PRODUCT_COPYRIGHT) 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | LSApplicationQueriesSchemes 32 | 33 | mailto 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /lib/search/view/full_text_search_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:otzaria/settings/settings_bloc.dart'; 4 | import 'package:otzaria/search/models/legacy_full_text_searcher.dart'; 5 | import 'package:otzaria/tabs/models/searching_tab.dart'; 6 | import 'package:otzaria/tabs/models/tab.dart'; 7 | import 'package:otzaria/search/view/tantivy_full_text_search.dart'; 8 | import 'package:otzaria/search/view/legacy_full_text_search_screen.dart'; 9 | 10 | class FullTextSearchScreen extends StatelessWidget { 11 | final void Function(OpenedTab) openBookCallback; 12 | final SearchingTab tab; 13 | const FullTextSearchScreen( 14 | {super.key, required this.tab, required this.openBookCallback}); 15 | @override 16 | Widget build(BuildContext context) { 17 | return context.read().state.useFastSearch 18 | ? BlocProvider.value( 19 | value: tab.searchBloc, 20 | child: TantivyFullTextSearch( 21 | tab: tab, 22 | ), 23 | ) 24 | : TextFileSearchScreen( 25 | searcher: FullTextSearcher( 26 | [], 27 | TextEditingController(), 28 | ValueNotifier([]), 29 | ), 30 | openBookCallback: openBookCallback, 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/workspaces/workspace_repository.dart: -------------------------------------------------------------------------------- 1 | import 'dart:developer' as developer; 2 | import 'package:hive/hive.dart'; 3 | import 'package:otzaria/workspaces/workspace.dart'; 4 | 5 | class WorkspaceRepository { 6 | static const String _workspacesBoxKey = 'key-workspaces'; 7 | 8 | (List, int) loadWorkspaces() { 9 | try { 10 | final box = Hive.box(name: 'workspaces'); 11 | final rawWorkspaces = 12 | box.get(_workspacesBoxKey, defaultValue: []) as List; 13 | final currentWorksapce = 14 | box.get("key-current-workspace", defaultValue: 0) as int; 15 | return ( 16 | List.from( 17 | rawWorkspaces.map((e) => Workspace.fromJson(e)).toList(), 18 | ), 19 | currentWorksapce 20 | ); 21 | } catch (e, stackTrace) { 22 | developer.log('Error loading workspaces from disk', 23 | error: e, stackTrace: stackTrace, name: 'WorkspaceRepository'); 24 | Hive.box(name: 'workspaces').put(_workspacesBoxKey, []); 25 | return ([], 0); 26 | } 27 | } 28 | 29 | void saveWorkspaces(List workspaces, int currentWorkspace) { 30 | final box = Hive.box(name: 'workspaces'); 31 | box.put(_workspacesBoxKey, 32 | workspaces.map((workspace) => workspace.toJson()).toList()); 33 | Hive.box(name: 'workspaces').put("key-current-workspace", currentWorkspace); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/services/data_collection_service_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:otzaria/services/data_collection_service.dart'; 3 | import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; 4 | 5 | void main() { 6 | group('DataCollectionService', () { 7 | late DataCollectionService service; 8 | 9 | setUp(() { 10 | service = DataCollectionService(); 11 | }); 12 | 13 | test('should return unknown when library version file is missing', 14 | () async { 15 | // This test would need proper mocking of file system 16 | // For now, we just test the basic structure 17 | expect(service, isA()); 18 | }); 19 | 20 | test('should calculate current line number correctly', () { 21 | final positions = [ 22 | ItemPosition(index: 5, itemLeadingEdge: 0.0, itemTrailingEdge: 1.0), 23 | ItemPosition(index: 3, itemLeadingEdge: 0.0, itemTrailingEdge: 1.0), 24 | ItemPosition(index: 7, itemLeadingEdge: 0.0, itemTrailingEdge: 1.0), 25 | ]; 26 | 27 | final lineNumber = service.getCurrentLineNumber(positions); 28 | expect(lineNumber, equals(4)); // 3 + 1 (1-based) 29 | }); 30 | 31 | test('should return 0 when no positions available', () { 32 | final lineNumber = service.getCurrentLineNumber([]); 33 | expect(lineNumber, equals(0)); 34 | }); 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /lib/widgets/filter_list/src/widget/search_field_widget.dart: -------------------------------------------------------------------------------- 1 | import '../theme/filter_list_theme.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:fluentui_system_icons/fluentui_system_icons.dart'; 4 | 5 | class SearchFieldWidget extends StatelessWidget { 6 | final ValueChanged onChanged; 7 | const SearchFieldWidget({ 8 | super.key, 9 | required this.onChanged, 10 | }); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | final headerTheme = FilterListTheme.of(context).headerTheme; 15 | return Material( 16 | color: Colors.transparent, 17 | child: Container( 18 | margin: const EdgeInsets.all(4), 19 | decoration: BoxDecoration( 20 | borderRadius: 21 | BorderRadius.circular(headerTheme.searchFieldBorderRadius), 22 | color: headerTheme.searchFieldBackgroundColor, 23 | ), 24 | child: TextField( 25 | onChanged: onChanged, 26 | style: headerTheme.searchFieldTextStyle, 27 | decoration: InputDecoration( 28 | prefixIcon: 29 | Icon(FluentIcons.search_24_regular, color: headerTheme.searchFieldIconColor), 30 | hintText: headerTheme.searchFieldHintText, 31 | hintStyle: headerTheme.searchFieldHintTextStyle, 32 | border: headerTheme.searchFieldInputBorder, 33 | ), 34 | ), 35 | ), 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/find_ref/find_ref_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:otzaria/models/books.dart'; // Import Book model 2 | import 'package:equatable/equatable.dart'; 3 | import 'package:search_engine/search_engine.dart'; 4 | 5 | abstract class FindRefState extends Equatable { 6 | const FindRefState(); 7 | 8 | @override 9 | List get props => []; 10 | } 11 | 12 | class FindRefInitial extends FindRefState {} 13 | 14 | class FindRefLoading extends FindRefState {} 15 | 16 | class FindRefSuccess extends FindRefState { 17 | final List refs; 18 | const FindRefSuccess(this.refs); 19 | 20 | @override 21 | List get props => [refs]; 22 | } 23 | 24 | class FindRefError extends FindRefState { 25 | final String message; 26 | const FindRefError(this.message); 27 | 28 | @override 29 | List get props => [message]; 30 | } 31 | 32 | class FindRefIndexingStatus extends FindRefState { 33 | final int? booksProcessed; 34 | final int? totalBooks; 35 | 36 | const FindRefIndexingStatus({this.booksProcessed, this.totalBooks}); 37 | 38 | @override 39 | List get props => [booksProcessed ?? 0, totalBooks ?? 0]; 40 | } 41 | 42 | class FindRefBookOpening extends FindRefState { 43 | // Add BookOpening state 44 | final Book book; 45 | final int index; 46 | 47 | const FindRefBookOpening({required this.book, required this.index}); 48 | 49 | @override 50 | List get props => [book, index]; 51 | } 52 | -------------------------------------------------------------------------------- /windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "utils.h" 7 | 8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 9 | _In_ wchar_t *command_line, _In_ int show_command) { 10 | // Attach to console when present (e.g., 'flutter run') or create a 11 | // new console when running with a debugger. 12 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 13 | CreateAndAttachConsole(); 14 | } 15 | 16 | // Initialize COM, so that it is available for use in the library and/or 17 | // plugins. 18 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 19 | 20 | flutter::DartProject project(L"data"); 21 | 22 | std::vector command_line_arguments = 23 | GetCommandLineArguments(); 24 | 25 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 26 | 27 | FlutterWindow window(project); 28 | Win32Window::Point origin(10, 10); 29 | Win32Window::Size size(1280, 720); 30 | if (!window.Create(L"אוצריא", origin, size)) { 31 | return EXIT_FAILURE; 32 | } 33 | window.SetQuitOnClose(true); 34 | 35 | ::MSG msg; 36 | while (::GetMessage(&msg, nullptr, 0, 0)) { 37 | ::TranslateMessage(&msg); 38 | ::DispatchMessage(&msg); 39 | } 40 | 41 | ::CoUninitialize(); 42 | return EXIT_SUCCESS; 43 | } 44 | -------------------------------------------------------------------------------- /lib/navigation/about_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:fluentui_system_icons/fluentui_system_icons.dart'; 3 | import 'package:otzaria/navigation/about_screen.dart'; 4 | 5 | class AboutDialogWidget extends StatelessWidget { 6 | const AboutDialogWidget({super.key}); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Dialog( 11 | insetPadding: const EdgeInsets.all(16), 12 | child: Container( 13 | width: MediaQuery.of(context).size.width * 0.8, 14 | height: MediaQuery.of(context).size.height * 0.8, 15 | padding: const EdgeInsets.all(16), 16 | child: Column( 17 | crossAxisAlignment: CrossAxisAlignment.start, 18 | children: [ 19 | Row( 20 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 21 | children: [ 22 | const Text( 23 | 'אודות התוכנה', 24 | style: TextStyle( 25 | fontSize: 24, 26 | fontWeight: FontWeight.bold, 27 | ), 28 | ), 29 | IconButton( 30 | icon: const Icon(FluentIcons.dismiss_24_regular), 31 | onPressed: () => Navigator.of(context).pop(), 32 | ), 33 | ], 34 | ), 35 | const SizedBox(height: 16), 36 | const Expanded(child: AboutScreen()), 37 | ], 38 | ), 39 | ), 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/workspaces/bloc/workspace_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:otzaria/workspaces/workspace.dart'; 3 | import 'package:otzaria/tabs/models/tab.dart'; 4 | 5 | abstract class WorkspaceEvent extends Equatable { 6 | const WorkspaceEvent(); 7 | 8 | @override 9 | List get props => []; 10 | } 11 | 12 | class LoadWorkspaces extends WorkspaceEvent {} 13 | 14 | class AddWorkspace extends WorkspaceEvent { 15 | final String name; 16 | final List tabs; 17 | final int currentTabIndex; 18 | 19 | const AddWorkspace({ 20 | required this.name, 21 | required this.tabs, 22 | required this.currentTabIndex, 23 | }); 24 | 25 | @override 26 | List get props => [name, tabs, currentTabIndex]; 27 | } 28 | 29 | class RemoveWorkspace extends WorkspaceEvent { 30 | final Workspace workspace; 31 | 32 | const RemoveWorkspace(this.workspace); 33 | 34 | @override 35 | List get props => [workspace]; 36 | } 37 | 38 | class SwitchToWorkspace extends WorkspaceEvent { 39 | final Workspace workspace; 40 | 41 | const SwitchToWorkspace(this.workspace); 42 | 43 | @override 44 | List get props => [workspace]; 45 | } 46 | 47 | class RenameWorkspace extends WorkspaceEvent { 48 | final Workspace workspace; 49 | final String newName; 50 | 51 | const RenameWorkspace(this.workspace, this.newName); 52 | 53 | @override 54 | List get props => [workspace, newName]; 55 | } 56 | 57 | class ClearWorkspaces extends WorkspaceEvent {} 58 | -------------------------------------------------------------------------------- /sivan22.crt.bak: -------------------------------------------------------------------------------- 1 | Bag Attributes 2 | localKeyID: 01 00 00 00 3 | 1.3.6.1.4.1.311.17.3.71: 44 00 45 00 53 00 4B 00 54 00 4F 00 50 00 2D 00 35 00 48 00 56 00 49 00 38 00 32 00 55 00 00 00 4 | friendlyName: sivan22's self-signed cert 5 | subject=C=IL, O=sivan22, CN=sivan22 6 | issuer=C=IL, O=sivan22, CN=sivan22 7 | -----BEGIN CERTIFICATE----- 8 | MIIDQDCCAiigAwIBAgIQQsUSoy13F5hASFMaJvLujDANBgkqhkiG9w0BAQsFADAx 9 | MQswCQYDVQQGEwJJTDEQMA4GA1UECgwHc2l2YW4yMjEQMA4GA1UEAwwHc2l2YW4y 10 | MjAeFw0yNDA0MDIxMTI1MzJaFw0yNTA0MDIxMTQ1MzJaMDExCzAJBgNVBAYTAklM 11 | MRAwDgYDVQQKDAdzaXZhbjIyMRAwDgYDVQQDDAdzaXZhbjIyMIIBIjANBgkqhkiG 12 | 9w0BAQEFAAOCAQ8AMIIBCgKCAQEArdOGNwnJmUEFmHgasWe3sBkBjhzX20qNfj99 13 | 73QRAKCczozqfjgQ5lbiIcgR6pFzCFR0nxVBehISAGWxs2ocmWabTcdSInY88OWJ 14 | wUBGUI3z5EKlsKfY9Sx3Tm3mjJAcgPC7+pL6bT8kaTSXLtaZ5axv0Sh3xFvXWJBG 15 | ozm4epeGtHp9IX0I3q489Z8c8MsXeenciXgGSZzO8iH30meLip+iRJs3xhoEspBU 16 | rRqdr8IgS/JeBmtjcLV1DbZLfBMEvJYucAu6e6rr3GyRPFl2pjnR/eeX7GhkL9q6 17 | ExNAuOJuhUqZotpupSHVLfJlb2GAtsbcccv5JeN0feqKJHU0uQIDAQABo1QwUjAO 18 | BgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwDAYDVR0TAQH/BAIw 19 | ADAdBgNVHQ4EFgQUX+eliaOYL/V6gedABgfPJVn/dxkwDQYJKoZIhvcNAQELBQAD 20 | ggEBACGFYZ3x43IlH4upSJ03LCWI3129OW3Jz1xlx4qMcmieTrA6vToegInzLQJf 21 | sqbathwW69gBZg/iAABulzrGZJKTWIFggSRQOg8PGXPyYeSrQi5v15JmImWMX28x 22 | PSOvU1KMQjygVU5eLOBkOj6RrVl93eCr5+HozUsC1p98MypymT0ZXjTtmeLB83Or 23 | zTbgTWBWRe/Mob+M2OvPOI5NoFh6JrihrvZgNN656nOZuTNYldxnZeD7ULICG9pE 24 | wKkROykswbqh2JWtM8vVbrB243+qtVcqPJTr4b2qLqTpG2y3CYSR14pUFJmvV2XK 25 | jfV+DFPBasUxni4RJlQp2Ekf67g= 26 | -----END CERTIFICATE----- 27 | -------------------------------------------------------------------------------- /lib/widgets/reusable_items_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:fluentui_system_icons/fluentui_system_icons.dart'; 3 | 4 | class ReusableItemsDialog extends StatelessWidget { 5 | final String title; 6 | final Widget child; 7 | 8 | const ReusableItemsDialog({ 9 | super.key, 10 | required this.title, 11 | required this.child, 12 | }); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return Dialog( 17 | insetPadding: const EdgeInsets.all(16), 18 | child: Container( 19 | width: MediaQuery.of(context).size.width * 0.8, 20 | height: MediaQuery.of(context).size.height * 0.8, 21 | padding: const EdgeInsets.all(16), 22 | child: Column( 23 | crossAxisAlignment: CrossAxisAlignment.start, 24 | children: [ 25 | Row( 26 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 27 | children: [ 28 | Text( 29 | title, 30 | style: const TextStyle( 31 | fontSize: 24, 32 | fontWeight: FontWeight.bold, 33 | ), 34 | ), 35 | IconButton( 36 | icon: const Icon(FluentIcons.dismiss_24_regular), 37 | onPressed: () => Navigator.of(context).pop(), 38 | ), 39 | ], 40 | ), 41 | const SizedBox(height: 16), 42 | Expanded(child: child), 43 | ], 44 | ), 45 | ), 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/navigation/favoriets_screen.dart: -------------------------------------------------------------------------------- 1 | // a widget that contains two tabs: history and bookmarks. 2 | // The bookmarks tab is BookmarkView and the history is HistoryView. 3 | import 'package:flutter/material.dart'; 4 | import 'package:fluentui_system_icons/fluentui_system_icons.dart'; 5 | import 'package:otzaria/history/history_screen.dart'; 6 | import 'package:otzaria/bookmarks/bookmark_screen.dart'; 7 | 8 | class FavouritesScreen extends StatefulWidget { 9 | const FavouritesScreen({super.key}); 10 | 11 | @override 12 | State createState() => _FavouritesScreenState(); 13 | } 14 | 15 | class _FavouritesScreenState extends State 16 | with AutomaticKeepAliveClientMixin { 17 | @override 18 | bool get wantKeepAlive => true; 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | super.build(context); 23 | return const DefaultTabController( 24 | length: 2, 25 | child: Scaffold( 26 | appBar: TabBar( 27 | tabs: [ 28 | Tab( 29 | text: 'סימניות', 30 | icon: Icon( 31 | FluentIcons.bookmark_24_regular, 32 | ), 33 | ), 34 | Tab( 35 | text: 'היסטוריה', 36 | icon: Icon( 37 | FluentIcons.history_24_regular, 38 | ), 39 | ), 40 | ], 41 | ), 42 | body: TabBarView( 43 | children: [ 44 | BookmarkView(), 45 | HistoryView(), 46 | ], 47 | ), 48 | ), 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '12.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | target 'RunnerTests' do 36 | inherit! :search_paths 37 | end 38 | end 39 | 40 | post_install do |installer| 41 | installer.pods_project.targets.each do |target| 42 | flutter_additional_ios_build_settings(target) 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/empty_library/bloc/empty_library_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | abstract class EmptyLibraryState extends Equatable { 4 | final bool isDownloading; 5 | final double downloadProgress; 6 | final String? selectedPath; 7 | final double downloadedMB; 8 | final double downloadSpeed; 9 | final String currentOperation; 10 | final bool isCancelling; 11 | final String? errorMessage; 12 | 13 | const EmptyLibraryState({ 14 | this.isDownloading = false, 15 | this.downloadProgress = 0, 16 | this.selectedPath, 17 | this.downloadedMB = 0, 18 | this.downloadSpeed = 0, 19 | this.currentOperation = '', 20 | this.isCancelling = false, 21 | this.errorMessage, 22 | }); 23 | 24 | @override 25 | List get props => [ 26 | isDownloading, 27 | downloadProgress, 28 | selectedPath, 29 | downloadedMB, 30 | downloadSpeed, 31 | currentOperation, 32 | isCancelling, 33 | errorMessage, 34 | ]; 35 | } 36 | 37 | class EmptyLibraryInitial extends EmptyLibraryState {} 38 | 39 | class EmptyLibraryLoading extends EmptyLibraryState { 40 | const EmptyLibraryLoading({ 41 | super.isDownloading, 42 | super.downloadProgress, 43 | super.selectedPath, 44 | super.downloadedMB, 45 | super.downloadSpeed, 46 | super.currentOperation, 47 | super.isCancelling, 48 | }); 49 | } 50 | 51 | class EmptyLibraryDownloaded extends EmptyLibraryState {} 52 | 53 | class EmptyLibraryError extends EmptyLibraryState { 54 | const EmptyLibraryError({super.errorMessage}); 55 | } 56 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "app_icon_16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "app_icon_32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "app_icon_32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "app_icon_64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "app_icon_128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "app_icon_256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "app_icon_256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "app_icon_512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "app_icon_512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "app_icon_1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /macos/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.15' 2 | 3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 5 | 6 | project 'Runner', { 7 | 'Debug' => :debug, 8 | 'Profile' => :release, 9 | 'Release' => :release, 10 | } 11 | 12 | def flutter_root 13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) 14 | unless File.exist?(generated_xcode_build_settings_path) 15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" 16 | end 17 | 18 | File.foreach(generated_xcode_build_settings_path) do |line| 19 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 20 | return matches[1].strip if matches 21 | end 22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" 23 | end 24 | 25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 26 | 27 | flutter_macos_podfile_setup 28 | 29 | target 'Runner' do 30 | use_frameworks! 31 | use_modular_headers! 32 | 33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) 34 | target 'RunnerTests' do 35 | inherit! :search_paths 36 | end 37 | end 38 | 39 | post_install do |installer| 40 | installer.pods_project.targets.each do |target| 41 | flutter_additional_macos_build_settings(target) 42 | target.build_configurations.each do |config| 43 | config.build_settings['MACOSX_DEPLOYMENT_TARGET'] = '10.15' 44 | end 45 | end 46 | end -------------------------------------------------------------------------------- /lib/widgets/filter_list/src/widget/control_button.dart: -------------------------------------------------------------------------------- 1 | import '../theme/theme.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class ControlButton extends StatelessWidget { 5 | const ControlButton({ 6 | super.key, 7 | required this.choiceChipLabel, 8 | this.onPressed, 9 | this.primaryButton = false, 10 | }); 11 | final String choiceChipLabel; 12 | final VoidCallback? onPressed; 13 | final bool primaryButton; 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | final theme = ControlButtonTheme.of(context); 18 | return TextButton( 19 | style: ButtonStyle( 20 | shape: WidgetStateProperty.all(RoundedRectangleBorder( 21 | borderRadius: BorderRadius.all(Radius.circular(theme.borderRadius)), 22 | )), 23 | backgroundColor: WidgetStateProperty.all( 24 | primaryButton 25 | ? theme.primaryButtonBackgroundColor 26 | : theme.backgroundColor, 27 | ), 28 | elevation: WidgetStateProperty.all(theme.elevation), 29 | foregroundColor: WidgetStateProperty.all( 30 | primaryButton 31 | ? theme.primaryButtonTextStyle!.color 32 | : theme.textStyle!.color, 33 | ), 34 | textStyle: WidgetStateProperty.all( 35 | primaryButton ? theme.primaryButtonTextStyle : theme.textStyle, 36 | ), 37 | padding: WidgetStateProperty.all(theme.padding), 38 | ), 39 | onPressed: onPressed, 40 | clipBehavior: Clip.antiAlias, 41 | child: Text( 42 | choiceChipLabel, 43 | textAlign: TextAlign.center, 44 | overflow: TextOverflow.ellipsis, 45 | ), 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/data/repository/hive_list_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:hive/hive.dart'; 2 | 3 | /// Generic repository for managing lists of objects in Hive. 4 | /// T must have `fromJson(Map)` and `toJson()` methods. 5 | class HiveListRepository { 6 | final String boxName; 7 | final String key; 8 | final T Function(Map) fromJson; 9 | final Map Function(T) toJson; 10 | 11 | HiveListRepository({ 12 | required this.boxName, 13 | required this.key, 14 | required this.fromJson, 15 | required this.toJson, 16 | }); 17 | 18 | Box get _box => Hive.box(name: boxName); 19 | 20 | /// Load the list from Hive 21 | Future> load() async { 22 | try { 23 | final List raw = 24 | _box.get(key, defaultValue: []) as List; 25 | return raw.map((e) => fromJson(Map.from(e))).toList(); 26 | } catch (e) { 27 | _box.put(key, []); 28 | return []; 29 | } 30 | } 31 | 32 | /// Save the list to Hive 33 | Future save(List items) async { 34 | _box.put(key, items.map(toJson).toList()); 35 | } 36 | 37 | /// Clear the list 38 | Future clear() async { 39 | _box.put(key, []); 40 | } 41 | 42 | /// Add an item at the beginning of the list 43 | Future addItem(T item) async { 44 | final list = await load(); 45 | list.insert(0, item); 46 | await save(list); 47 | } 48 | 49 | /// Remove item at index 50 | Future removeAt(int index) async { 51 | final list = await load(); 52 | if (index >= 0 && index < list.length) { 53 | list.removeAt(index); 54 | await save(list); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | void RegisterPlugins(flutter::PluginRegistry* registry) { 19 | IrondashEngineContextPluginCApiRegisterWithRegistrar( 20 | registry->GetRegistrarForPlugin("IrondashEngineContextPluginCApi")); 21 | IsarFlutterLibsPluginRegisterWithRegistrar( 22 | registry->GetRegistrarForPlugin("IsarFlutterLibsPlugin")); 23 | PrintingPluginRegisterWithRegistrar( 24 | registry->GetRegistrarForPlugin("PrintingPlugin")); 25 | ScreenRetrieverPluginRegisterWithRegistrar( 26 | registry->GetRegistrarForPlugin("ScreenRetrieverPlugin")); 27 | Sqlite3FlutterLibsPluginRegisterWithRegistrar( 28 | registry->GetRegistrarForPlugin("Sqlite3FlutterLibsPlugin")); 29 | SuperNativeExtensionsPluginCApiRegisterWithRegistrar( 30 | registry->GetRegistrarForPlugin("SuperNativeExtensionsPluginCApi")); 31 | UrlLauncherWindowsRegisterWithRegistrar( 32 | registry->GetRegistrarForPlugin("UrlLauncherWindows")); 33 | WindowManagerPluginRegisterWithRegistrar( 34 | registry->GetRegistrarForPlugin("WindowManagerPlugin")); 35 | } 36 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /lib/navigation/bloc/navigation_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_bloc/flutter_bloc.dart'; 2 | import 'package:otzaria/navigation/bloc/navigation_event.dart'; 3 | import 'package:otzaria/navigation/navigation_repository.dart'; 4 | import 'package:otzaria/navigation/bloc/navigation_state.dart'; 5 | // ignore: unused_import 6 | import 'package:otzaria/tabs/bloc/tabs_bloc.dart'; 7 | import 'package:otzaria/tabs/tabs_repository.dart'; 8 | 9 | class NavigationBloc extends Bloc { 10 | final NavigationRepository _repository; 11 | 12 | NavigationBloc({ 13 | required NavigationRepository repository, 14 | required TabsRepository tabsRepository, 15 | }) : _repository = repository, 16 | super(NavigationState.initial(tabsRepository.loadTabs().isNotEmpty)) { 17 | on(_onNavigateToScreen); 18 | on(_onCheckLibrary); 19 | on(_onUpdateLibraryStatus); 20 | } 21 | 22 | void _onNavigateToScreen( 23 | NavigateToScreen event, 24 | Emitter emit, 25 | ) { 26 | emit(state.copyWith(currentScreen: event.screen)); 27 | } 28 | 29 | void _onCheckLibrary( 30 | CheckLibrary event, 31 | Emitter emit, 32 | ) { 33 | final isEmpty = _repository.checkLibraryIsEmpty(); 34 | emit(state.copyWith(isLibraryEmpty: isEmpty)); 35 | } 36 | 37 | void _onUpdateLibraryStatus( 38 | UpdateLibraryStatus event, 39 | Emitter emit, 40 | ) { 41 | emit(state.copyWith(isLibraryEmpty: event.isEmpty)); 42 | } 43 | 44 | Future refreshLibrary() async { 45 | await _repository.refreshLibrary(); 46 | add(const CheckLibrary()); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/focus/focus_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | enum FocusTarget { none, librarySearch, findRefSearch } 4 | 5 | class FocusRepository { 6 | static final FocusRepository _instance = FocusRepository._internal(); 7 | factory FocusRepository() => _instance; 8 | FocusRepository._internal(); 9 | 10 | final FocusNode librarySearchFocusNode = FocusNode(); 11 | final FocusNode findRefSearchFocusNode = FocusNode(); 12 | 13 | final TextEditingController librarySearchController = TextEditingController(); 14 | final TextEditingController findRefSearchController = TextEditingController(); 15 | 16 | FocusTarget _currentFocusTarget = FocusTarget.none; 17 | FocusTarget get currentFocusTarget => _currentFocusTarget; 18 | 19 | void requestLibrarySearchFocus({bool selectAll = false}) { 20 | librarySearchFocusNode.requestFocus(); 21 | if (selectAll) { 22 | librarySearchController.selection = TextSelection( 23 | baseOffset: 0, 24 | extentOffset: librarySearchController.text.length, 25 | ); 26 | } 27 | _currentFocusTarget = FocusTarget.librarySearch; 28 | } 29 | 30 | void requestFindRefSearchFocus({bool selectAll = false}) { 31 | findRefSearchFocusNode.requestFocus(); 32 | if (selectAll) { 33 | findRefSearchController.selection = TextSelection( 34 | baseOffset: 0, 35 | extentOffset: findRefSearchController.text.length, 36 | ); 37 | } 38 | _currentFocusTarget = FocusTarget.findRefSearch; 39 | } 40 | 41 | 42 | 43 | void dispose() { 44 | librarySearchFocusNode.dispose(); 45 | findRefSearchFocusNode.dispose(); 46 | librarySearchController.dispose(); 47 | findRefSearchController.dispose(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/text_book/editing/repository/overrides_repository.dart: -------------------------------------------------------------------------------- 1 | import '../models/text_override.dart'; 2 | import '../models/text_draft.dart'; 3 | 4 | /// Abstract repository for managing text overrides and drafts 5 | abstract class OverridesRepository { 6 | /// Reads an override for the specified book and section 7 | Future readOverride(String bookId, String sectionId); 8 | 9 | /// Writes an override for the specified book and section 10 | Future writeOverride(String bookId, String sectionId, String markdown, String sourceHash); 11 | 12 | /// Reads a draft for the specified book and section 13 | Future readDraft(String bookId, String sectionId); 14 | 15 | /// Writes a draft for the specified book and section 16 | Future writeDraft(String bookId, String sectionId, String markdown); 17 | 18 | /// Deletes a draft for the specified book and section 19 | Future deleteDraft(String bookId, String sectionId); 20 | 21 | /// Checks if there's a newer draft than the saved override 22 | Future hasNewerDraftThanOverride(String bookId, String sectionId); 23 | 24 | /// Checks if the specified book has a links file (commentary links) 25 | Future hasLinksFile(String bookId); 26 | 27 | /// Lists all overrides for a book 28 | Future> listOverrides(String bookId); 29 | 30 | /// Lists all drafts for a book 31 | Future> listDrafts(String bookId); 32 | 33 | /// Cleans up old drafts based on age and size limits 34 | Future cleanupOldDrafts(); 35 | 36 | /// Gets the total size of all drafts in MB 37 | Future getTotalDraftsSizeMB(); 38 | 39 | /// Deletes an override 40 | Future deleteOverride(String bookId, String sectionId); 41 | } -------------------------------------------------------------------------------- /lib/bookmarks/bloc/bookmark_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_bloc/flutter_bloc.dart'; 2 | import 'package:otzaria/bookmarks/bloc/bookmark_state.dart'; 3 | import 'package:otzaria/bookmarks/models/bookmark.dart'; 4 | import 'package:otzaria/bookmarks/repository/bookmark_repository.dart'; 5 | import 'package:otzaria/models/books.dart'; 6 | 7 | class BookmarkBloc extends Cubit { 8 | final BookmarkRepository _repository; 9 | 10 | BookmarkBloc(this._repository) : super(BookmarkState.initial()) { 11 | _loadBookmarks(); 12 | } 13 | 14 | void _loadBookmarks() async { 15 | try { 16 | final bookmarks = await _repository.loadBookmarks(); 17 | emit(state.copyWith(bookmarks: bookmarks)); 18 | } catch (e) { 19 | // handle error if needed 20 | } 21 | } 22 | 23 | bool addBookmark( 24 | {required String ref, 25 | required Book book, 26 | required int index, 27 | List? commentatorsToShow}) { 28 | final bookmark = Bookmark( 29 | ref: ref, 30 | book: book, 31 | index: index, 32 | commentatorsToShow: commentatorsToShow ?? []); 33 | // check if bookmark already exists 34 | if (state.bookmarks.any((b) => b.ref == bookmark.ref)) return false; 35 | 36 | final newBookmarks = [...state.bookmarks, bookmark]; 37 | _repository.saveBookmarks(newBookmarks); 38 | emit(state.copyWith(bookmarks: newBookmarks)); 39 | return true; 40 | } 41 | 42 | void removeBookmark(int index) { 43 | final newBookmarks = [...state.bookmarks]..removeAt(index); 44 | _repository.saveBookmarks(newBookmarks); 45 | emit(state.copyWith(bookmarks: newBookmarks)); 46 | } 47 | 48 | void clearBookmarks() { 49 | _repository.clearBookmarks(); 50 | emit(state.copyWith(bookmarks: [])); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/text_book/view/text_book_scaffold.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:otzaria/tabs/models/tab.dart'; 4 | import 'package:otzaria/tabs/models/text_tab.dart'; 5 | import 'package:otzaria/text_book/bloc/text_book_bloc.dart'; 6 | import 'package:otzaria/text_book/bloc/text_book_state.dart'; 7 | import 'package:otzaria/text_book/view/splited_view/splited_view_screen.dart'; 8 | 9 | class TextBookScaffold extends StatelessWidget { 10 | final List content; 11 | final Function(OpenedTab) openBookCallback; 12 | final void Function(int) openLeftPaneTab; 13 | final TextEditingValue searchTextController; 14 | final TextBookTab tab; 15 | final int? initialSidebarTabIndex; 16 | 17 | const TextBookScaffold({ 18 | super.key, 19 | required this.content, 20 | required this.openBookCallback, 21 | required this.openLeftPaneTab, 22 | required this.searchTextController, 23 | required this.tab, 24 | this.initialSidebarTabIndex, 25 | }); 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return BlocBuilder( 30 | builder: (context, state) { 31 | if (state is! TextBookLoaded) { 32 | return const Center(child: CircularProgressIndicator()); 33 | } 34 | 35 | // תמיד משתמשים ב-SplitedViewScreen, הוא יחליט אם להציג split או לא 36 | return SplitedViewScreen( 37 | content: content, 38 | openBookCallback: openBookCallback, 39 | searchTextController: searchTextController, 40 | openLeftPaneTab: openLeftPaneTab, 41 | tab: tab, 42 | initialTabIndex: initialSidebarTabIndex, 43 | showSplitView: state.showSplitView, 44 | ); 45 | }, 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/history/history_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:otzaria/bookmarks/models/bookmark.dart'; 2 | import 'package:otzaria/data/repository/base_list_repository.dart'; 3 | import 'package:otzaria/tabs/models/pdf_tab.dart'; 4 | import 'package:otzaria/tabs/models/tab.dart'; 5 | import 'package:otzaria/tabs/models/text_tab.dart'; 6 | import 'package:otzaria/text_book/bloc/text_book_state.dart'; 7 | import 'package:otzaria/utils/ref_helper.dart'; 8 | 9 | class HistoryRepository extends BaseListRepository { 10 | HistoryRepository() 11 | : super( 12 | boxName: 'history', 13 | key: 'history', 14 | fromJson: (json) => Bookmark.fromJson(json), 15 | toJson: (bookmark) => bookmark.toJson(), 16 | ); 17 | 18 | Future> loadHistory() async => load(); 19 | 20 | Future saveHistory(List history) async => save(history); 21 | 22 | Future clearHistory() async => clear(); 23 | 24 | Future addHistoryItem(Bookmark bookmark) async => addItem(bookmark); 25 | 26 | Future removeHistoryItem(int index) async => removeAt(index); 27 | 28 | Future addHistoryFromTab(OpenedTab tab) async { 29 | if (tab is PdfBookTab) { 30 | int index = tab.pdfViewerController.pageNumber ?? 1; 31 | addHistoryItem(Bookmark( 32 | ref: '${tab.title} עמוד $index', 33 | book: tab.book, 34 | index: index, 35 | )); 36 | } 37 | if (tab is TextBookTab) { 38 | final state = tab.bloc.state; 39 | if (state is TextBookLoaded) { 40 | final index = state.positionsListener.itemPositions.value.first.index; 41 | addHistoryItem(Bookmark( 42 | ref: await refFromIndex(index, tab.book.tableOfContents), 43 | book: tab.book, 44 | index: index, 45 | )); 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/tabs/bloc/tabs_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:otzaria/tabs/models/tab.dart'; 3 | 4 | abstract class TabsEvent extends Equatable { 5 | const TabsEvent(); 6 | 7 | @override 8 | List get props => []; 9 | } 10 | 11 | class AddTab extends TabsEvent { 12 | final OpenedTab tab; 13 | 14 | const AddTab(this.tab); 15 | 16 | @override 17 | List get props => [tab]; 18 | } 19 | 20 | class SaveTabs extends TabsEvent { 21 | const SaveTabs(); 22 | 23 | @override 24 | List get props => []; 25 | } 26 | 27 | class RemoveTab extends TabsEvent { 28 | final OpenedTab tab; 29 | 30 | const RemoveTab(this.tab); 31 | 32 | @override 33 | List get props => [tab]; 34 | } 35 | 36 | class CloseCurrentTab extends TabsEvent { 37 | const CloseCurrentTab(); 38 | 39 | @override 40 | List get props => []; 41 | } 42 | 43 | class SetCurrentTab extends TabsEvent { 44 | final int index; 45 | 46 | const SetCurrentTab(this.index); 47 | 48 | @override 49 | List get props => [index]; 50 | } 51 | 52 | class CloseAllTabs extends TabsEvent {} 53 | 54 | class CloseOtherTabs extends TabsEvent { 55 | final OpenedTab keepTab; 56 | 57 | const CloseOtherTabs(this.keepTab); 58 | 59 | @override 60 | List get props => [keepTab]; 61 | } 62 | 63 | class CloneTab extends TabsEvent { 64 | final OpenedTab tab; 65 | 66 | const CloneTab(this.tab); 67 | 68 | @override 69 | List get props => [tab]; 70 | } 71 | 72 | class MoveTab extends TabsEvent { 73 | final OpenedTab tab; 74 | final int newIndex; 75 | 76 | const MoveTab(this.tab, this.newIndex); 77 | 78 | @override 79 | List get props => [tab, newIndex]; 80 | } 81 | 82 | class NavigateToNextTab extends TabsEvent {} 83 | 84 | class NavigateToPreviousTab extends TabsEvent {} 85 | 86 | class LoadTabs extends TabsEvent {} 87 | -------------------------------------------------------------------------------- /webhooks/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | 4 | from pyluach import dates 5 | 6 | from mitmachim import MitmachimClient 7 | from yemot import split_and_send 8 | 9 | 10 | def heb_date() -> str: 11 | today = dates.HebrewDate.today() 12 | date_str = today.hebrew_date_string() 13 | return date_str 14 | 15 | 16 | date_str = heb_date() 17 | RELEASE_TAG = os.getenv("RELEASE_TAG", "Unknown") 18 | RELEASE_NAME = os.getenv("RELEASE_NAME", "No Name") 19 | RELEASE_BODY = os.getenv("RELEASE_BODY", "") 20 | RELEASE_URL = os.getenv("RELEASE_URL", "") 21 | GITHUB_EVENT_PATH = os.getenv("GITHUB_EVENT_PATH") 22 | username = os.getenv("USER_NAME") 23 | password = os.getenv("PASSWORD") 24 | yemot_token = os.getenv("TOKEN_YEMOT") 25 | asset_links = [] 26 | if GITHUB_EVENT_PATH: 27 | with open(GITHUB_EVENT_PATH, "r", encoding="utf-8") as f: 28 | event_data = json.load(f) 29 | assets = event_data.get("release", {}).get("assets", []) 30 | for asset in assets: 31 | asset_links.append(f"[{asset['name']}]({asset['browser_download_url']})") 32 | date_yemot = f"עדכון {date_str}\n" 33 | yemot_path = "ivr2:/2" 34 | tzintuk_list_name = "software update" 35 | yemot_message = f"עדכון {date_str}\nשחרור {RELEASE_NAME}\nפרטים: {RELEASE_BODY}\n" 36 | content_mitmachim = f"עדכון {date_str}\nשחרור {RELEASE_NAME}\nפרטים: {RELEASE_BODY}\n{RELEASE_URL}\nקבצים מצורפים:\n* {"\n* ".join(asset_links)}" 37 | 38 | client = MitmachimClient(username.strip().replace(" ", "+"), password.strip()) 39 | if asset_links: 40 | try: 41 | client.login() 42 | topic_id = 87961 43 | client.send_post(content_mitmachim, topic_id) 44 | except Exception as e: 45 | print(e) 46 | finally: 47 | client.logout() 48 | 49 | try: 50 | split_and_send(yemot_message, date_yemot, yemot_token, yemot_path, tzintuk_list_name) 51 | except Exception as e: 52 | print(e) 53 | -------------------------------------------------------------------------------- /lib/personal_notes/bloc/personal_notes_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | import 'package:otzaria/personal_notes/migration/legacy_notes_converter.dart'; 4 | import 'package:otzaria/personal_notes/models/personal_note.dart'; 5 | 6 | class PersonalNotesState extends Equatable { 7 | final bool isLoading; 8 | final String? bookId; 9 | final List locatedNotes; 10 | final List missingNotes; 11 | final String? errorMessage; 12 | final LegacyConversionSummary? conversionSummary; 13 | 14 | const PersonalNotesState({ 15 | required this.isLoading, 16 | required this.bookId, 17 | required this.locatedNotes, 18 | required this.missingNotes, 19 | required this.errorMessage, 20 | required this.conversionSummary, 21 | }); 22 | 23 | const PersonalNotesState.initial() 24 | : isLoading = false, 25 | bookId = null, 26 | locatedNotes = const [], 27 | missingNotes = const [], 28 | errorMessage = null, 29 | conversionSummary = null; 30 | 31 | PersonalNotesState copyWith({ 32 | bool? isLoading, 33 | String? bookId, 34 | List? locatedNotes, 35 | List? missingNotes, 36 | String? errorMessage, 37 | LegacyConversionSummary? conversionSummary, 38 | }) { 39 | return PersonalNotesState( 40 | isLoading: isLoading ?? this.isLoading, 41 | bookId: bookId ?? this.bookId, 42 | locatedNotes: locatedNotes ?? this.locatedNotes, 43 | missingNotes: missingNotes ?? this.missingNotes, 44 | errorMessage: errorMessage, 45 | conversionSummary: conversionSummary ?? this.conversionSummary, 46 | ); 47 | } 48 | 49 | @override 50 | List get props => [ 51 | isLoading, 52 | bookId, 53 | locatedNotes, 54 | missingNotes, 55 | errorMessage, 56 | conversionSummary, 57 | ]; 58 | } 59 | -------------------------------------------------------------------------------- /windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(runner LANGUAGES CXX) 3 | 4 | # Define the application target. To change its name, change BINARY_NAME in the 5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer 6 | # work. 7 | # 8 | # Any new source files that you add to the application should be added here. 9 | add_executable(${BINARY_NAME} WIN32 10 | "flutter_window.cpp" 11 | "main.cpp" 12 | "utils.cpp" 13 | "win32_window.cpp" 14 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 15 | "Runner.rc" 16 | "runner.exe.manifest" 17 | ) 18 | 19 | # Apply the standard set of build settings. This can be removed for applications 20 | # that need different build settings. 21 | apply_standard_settings(${BINARY_NAME}) 22 | 23 | # Add preprocessor definitions for the build version. 24 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") 25 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") 26 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") 27 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") 28 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") 29 | 30 | # Disable Windows macros that collide with C++ standard library functions. 31 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 32 | 33 | # Add dependency libraries and include directories. Add any application-specific 34 | # dependencies here. 35 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 36 | target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") 37 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 38 | 39 | # Run the Flutter tool portions of the build. This must not be removed. 40 | add_dependencies(${BINARY_NAME} flutter_assemble) 41 | -------------------------------------------------------------------------------- /lib/find_ref/find_ref_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_bloc/flutter_bloc.dart'; 2 | import 'package:otzaria/find_ref/find_ref_event.dart'; 3 | import 'package:otzaria/find_ref/find_ref_repository.dart'; 4 | import 'package:otzaria/find_ref/find_ref_state.dart'; 5 | import 'package:otzaria/models/books.dart'; 6 | import 'package:search_engine/search_engine.dart'; 7 | 8 | class FindRefBloc extends Bloc { 9 | final FindRefRepository findRefRepository; 10 | 11 | FindRefBloc({required this.findRefRepository}) : super(FindRefInitial()) { 12 | on(_onSearchRefRequested); 13 | on(_onClearSearchRequested); 14 | on(_onOpenBookRequested); 15 | } 16 | 17 | Future _onSearchRefRequested( 18 | SearchRefRequested event, Emitter emit) async { 19 | if (event.refText.length < 3) { 20 | emit(const FindRefSuccess([])); 21 | return; 22 | } 23 | emit(FindRefLoading()); 24 | try { 25 | final List refs = 26 | await findRefRepository.findRefs(event.refText); 27 | emit(FindRefSuccess(refs)); 28 | } catch (e) { 29 | emit(FindRefError(e.toString())); 30 | } 31 | } 32 | 33 | void _onClearSearchRequested( 34 | ClearSearchRequested event, Emitter emit) { 35 | emit(FindRefInitial()); 36 | } 37 | 38 | void _onOpenBookRequested( 39 | OpenBookRequested event, Emitter emit) { 40 | final book = event.book; 41 | final index = event.index; 42 | emit( 43 | FindRefBookOpening(book: book, index: index)); // Emit BookOpening state 44 | } 45 | } 46 | 47 | class FindRefBookOpening extends FindRefState { 48 | // Define BookOpening state 49 | final Book book; 50 | final int index; 51 | 52 | const FindRefBookOpening({required this.book, required this.index}); 53 | 54 | @override 55 | List get props => [book, index]; 56 | } 57 | -------------------------------------------------------------------------------- /webhooks/yemot.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | 4 | BASE_URL = "https://www.call2all.co.il/ym/api/" 5 | 6 | 7 | def split_content(content: str) -> list[str]: 8 | all_partes = [] 9 | start = 0 10 | chunk_size = 2000 11 | while len(content) - start > chunk_size: 12 | part = content[start:content.rfind("\n", start, start + chunk_size)] 13 | all_partes.append(part.strip()) 14 | start += len(part) 15 | all_partes.append(content[start:].strip()) 16 | return all_partes 17 | 18 | 19 | def split_and_send(content: str, date_yemot: str, token: str, path: str, tzintuk_list_name: str): 20 | num = get_file_num(token, path) 21 | all_partes = split_content(content) 22 | for chunk in all_partes[-1::-1]: 23 | num += 1 24 | file_name = str(num).zfill(3) 25 | send_to_yemot(chunk, token, path, file_name) 26 | send_to_yemot(date_yemot, token, path, f"{file_name}-Title") 27 | send_tzintuk(token, tzintuk_list_name) 28 | 29 | 30 | def send_to_yemot(content: str, token: str, path: str, file_name: str) -> int: 31 | url = f"{BASE_URL}UploadTextFile" 32 | data = { 33 | "token": token, 34 | "what": f"{path}/{file_name}.tts", 35 | "contents": content 36 | } 37 | response = requests.post(url, data=data) 38 | return response.status_code 39 | 40 | 41 | def get_file_num(token: str, path: str) -> int: 42 | url = f"{BASE_URL}GetIVR2DirStats" 43 | data = { 44 | "token": token, 45 | "path": path 46 | } 47 | response = requests.get(url, params=data).json() 48 | try: 49 | max_file = response["maxFile"]["name"] 50 | return int(max_file.split(".")[0]) 51 | except: 52 | return -1 53 | 54 | 55 | def send_tzintuk(token: str, list_name: str) -> int: 56 | url = f"{BASE_URL}RunTzintuk" 57 | data = { 58 | "token": token, 59 | "phones": f"tzl:{list_name}" 60 | } 61 | response = requests.get(url, params=data) 62 | return response.status_code 63 | -------------------------------------------------------------------------------- /lib/library/bloc/library_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:otzaria/library/models/library.dart'; 3 | import 'package:otzaria/models/books.dart'; 4 | 5 | abstract class LibraryEvent extends Equatable { 6 | const LibraryEvent(); 7 | 8 | @override 9 | List get props => []; 10 | } 11 | 12 | class LoadLibrary extends LibraryEvent {} 13 | 14 | class RefreshLibrary extends LibraryEvent {} 15 | 16 | class UpdateLibraryPath extends LibraryEvent { 17 | final String path; 18 | 19 | const UpdateLibraryPath(this.path); 20 | 21 | @override 22 | List get props => [path]; 23 | } 24 | 25 | class UpdateHebrewBooksPath extends LibraryEvent { 26 | final String path; 27 | 28 | const UpdateHebrewBooksPath(this.path); 29 | 30 | @override 31 | List get props => [path]; 32 | } 33 | 34 | class NavigateToCategory extends LibraryEvent { 35 | final Category category; 36 | 37 | const NavigateToCategory(this.category); 38 | 39 | @override 40 | List get props => [category]; 41 | } 42 | 43 | class NavigateUp extends LibraryEvent {} 44 | 45 | class SearchBooks extends LibraryEvent { 46 | final bool? showOtzarHachochma; 47 | final bool? showHebrewBooks; 48 | 49 | const SearchBooks({this.showOtzarHachochma, this.showHebrewBooks}); 50 | 51 | @override 52 | List get props => [showOtzarHachochma, showHebrewBooks]; 53 | } 54 | 55 | class UpdateSearchQuery extends LibraryEvent { 56 | final String query; 57 | 58 | const UpdateSearchQuery(this.query); 59 | 60 | @override 61 | List get props => [query]; 62 | } 63 | 64 | class SelectTopics extends LibraryEvent { 65 | final List topics; 66 | 67 | const SelectTopics(this.topics); 68 | 69 | @override 70 | List get props => [topics]; 71 | } 72 | 73 | class SelectBookForPreview extends LibraryEvent { 74 | final Book book; 75 | 76 | const SelectBookForPreview(this.book); 77 | 78 | @override 79 | List get props => [book]; 80 | } 81 | -------------------------------------------------------------------------------- /lib/personal_notes/bloc/personal_notes_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | abstract class PersonalNotesEvent extends Equatable { 4 | const PersonalNotesEvent(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class LoadPersonalNotes extends PersonalNotesEvent { 11 | final String bookId; 12 | 13 | const LoadPersonalNotes(this.bookId); 14 | 15 | @override 16 | List get props => [bookId]; 17 | } 18 | 19 | class AddPersonalNote extends PersonalNotesEvent { 20 | final String bookId; 21 | final int lineNumber; 22 | final String content; 23 | 24 | const AddPersonalNote({ 25 | required this.bookId, 26 | required this.lineNumber, 27 | required this.content, 28 | }); 29 | 30 | @override 31 | List get props => [bookId, lineNumber, content]; 32 | } 33 | 34 | class UpdatePersonalNote extends PersonalNotesEvent { 35 | final String bookId; 36 | final String noteId; 37 | final String content; 38 | 39 | const UpdatePersonalNote({ 40 | required this.bookId, 41 | required this.noteId, 42 | required this.content, 43 | }); 44 | 45 | @override 46 | List get props => [bookId, noteId, content]; 47 | } 48 | 49 | class DeletePersonalNote extends PersonalNotesEvent { 50 | final String bookId; 51 | final String noteId; 52 | 53 | const DeletePersonalNote({ 54 | required this.bookId, 55 | required this.noteId, 56 | }); 57 | 58 | @override 59 | List get props => [bookId, noteId]; 60 | } 61 | 62 | class RepositionPersonalNote extends PersonalNotesEvent { 63 | final String bookId; 64 | final String noteId; 65 | final int lineNumber; 66 | 67 | const RepositionPersonalNote({ 68 | required this.bookId, 69 | required this.noteId, 70 | required this.lineNumber, 71 | }); 72 | 73 | @override 74 | List get props => [bookId, noteId, lineNumber]; 75 | } 76 | 77 | class ConvertLegacyNotes extends PersonalNotesEvent { 78 | const ConvertLegacyNotes(); 79 | } 80 | -------------------------------------------------------------------------------- /lib/text_book/text_book_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:otzaria/data/data_providers/file_system_data_provider.dart'; 2 | import 'package:otzaria/models/books.dart'; 3 | import 'package:otzaria/models/links.dart'; 4 | import 'package:otzaria/utils/text_manipulation.dart'; 5 | 6 | class TextBookRepository { 7 | final FileSystemData _fileSystem; 8 | 9 | TextBookRepository({ 10 | required FileSystemData fileSystem, 11 | }) : _fileSystem = fileSystem; 12 | 13 | Future getBookContent(TextBook book) async { 14 | return await book.text; 15 | } 16 | 17 | Future> getBookLinks(TextBook book) async { 18 | return await book.links; 19 | } 20 | 21 | Future> getTableOfContents(TextBook book) async { 22 | return await book.tableOfContents; 23 | } 24 | 25 | Future> getAvailableCommentators(List links) async { 26 | List filteredLinks = links 27 | .where((link) => 28 | link.connectionType == 'commentary' || 29 | link.connectionType == 'targum') 30 | .toList(); 31 | 32 | List paths = filteredLinks.map((e) => e.path2).toList(); 33 | List uniquePaths = paths.toSet().toList(); 34 | List commentatorTitles = uniquePaths 35 | .map( 36 | (e) => getTitleFromPath(e), 37 | ) 38 | .toList(); 39 | 40 | // Filter commentators asynchronously 41 | List availableCommentators = []; 42 | for (String title in commentatorTitles) { 43 | if (await _fileSystem.bookExists(title)) { 44 | availableCommentators.add(title); 45 | } 46 | } 47 | 48 | availableCommentators.sort( 49 | (a, b) => a.compareTo(b), 50 | ); 51 | return availableCommentators; 52 | } 53 | 54 | Future bookExists(String title) async { 55 | return await _fileSystem.bookExists(title); 56 | } 57 | 58 | Future saveBookContent(TextBook book, String content) async { 59 | await _fileSystem.saveBookText(book.title, content); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 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 | -------------------------------------------------------------------------------- /test/calendar_cubit_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:otzaria/navigation/calendar_cubit.dart'; 3 | import 'package:kosher_dart/kosher_dart.dart'; 4 | 5 | void main() { 6 | group('CalendarCubit Jewish month navigation', () { 7 | test('next month handles Adar I -> Adar II and no year rollover to Nissan', () { 8 | // Create a JewishDate for a known leap year at Adar I (month 12) 9 | final jewish = JewishDate(); 10 | // 5784 is a leap year in the 19-year cycle 11 | jewish.setJewishDate(5784, 12, 15); // Middle of Adar I 12 | 13 | expect(jewish.isJewishLeapYear(), isTrue); 14 | expect(jewish.getJewishMonth(), 12); 15 | 16 | final next = computeNextJewishMonth(jewish); 17 | expect(next.getJewishYear(), 5784); 18 | expect(next.getJewishMonth(), 13, reason: 'Should move to Adar II same year'); 19 | 20 | final afterAdarII = computeNextJewishMonth(next); 21 | expect(afterAdarII.getJewishYear(), 5784, reason: 'After Adar II go to Nissan in same Jewish year'); 22 | expect(afterAdarII.getJewishMonth(), 1); 23 | }); 24 | 25 | test('previous month handles Nissan -> last Adar in same year', () { 26 | // Nissan of a leap year 27 | final nissan = JewishDate(); 28 | nissan.setJewishDate(5784, 1, 7); 29 | expect(nissan.isJewishLeapYear(), isTrue); 30 | expect(nissan.getJewishMonth(), 1); 31 | 32 | final prev = computePreviousJewishMonth(nissan); 33 | expect(prev.getJewishYear(), 5784, reason: 'Nissan -> Adar stays in same Jewish year'); 34 | expect(prev.getJewishMonth(), 13, reason: '5784 is leap; previous month is Adar II'); 35 | }); 36 | 37 | test('previous month handles Adar II -> Adar I within leap year', () { 38 | final adarII = JewishDate(); 39 | adarII.setJewishDate(5784, 13, 3); 40 | expect(adarII.isJewishLeapYear(), isTrue); 41 | final prev = computePreviousJewishMonth(adarII); 42 | expect(prev.getJewishMonth(), 12); 43 | expect(prev.getJewishYear(), 5784); 44 | }); 45 | }); 46 | } -------------------------------------------------------------------------------- /windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector GetCommandLineArguments() { 25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 26 | int argc; 27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 28 | if (argv == nullptr) { 29 | return std::vector(); 30 | } 31 | 32 | std::vector command_line_arguments; 33 | 34 | // Skip the first argument as it's the binary name. 35 | for (int i = 1; i < argc; i++) { 36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 37 | } 38 | 39 | ::LocalFree(argv); 40 | 41 | return command_line_arguments; 42 | } 43 | 44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 45 | if (utf16_string == nullptr) { 46 | return std::string(); 47 | } 48 | int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr) 51 | -1; // remove the trailing null character 52 | int input_length = (int)wcslen(utf16_string); 53 | std::string utf8_string; 54 | if (target_length <= 0 || target_length > utf8_string.max_size()) { 55 | return utf8_string; 56 | } 57 | utf8_string.resize(target_length); 58 | int converted_length = ::WideCharToMultiByte( 59 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 60 | input_length, utf8_string.data(), target_length, nullptr, nullptr); 61 | if (converted_length == 0) { 62 | return std::string(); 63 | } 64 | return utf8_string; 65 | } 66 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | otzaria 33 | 34 | 35 | 39 | 40 | 41 | 42 | 43 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /lib/text_book/editing/models/editor_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | /// Represents the current state of the text editor 4 | class EditorState extends Equatable { 5 | /// Whether the editor dialog is currently open 6 | final bool isOpen; 7 | 8 | /// Index of the section currently being edited 9 | final int? currentIndex; 10 | 11 | /// Section ID of the section currently being edited 12 | final String? currentSectionId; 13 | 14 | /// Current text content in the editor 15 | final String? currentText; 16 | 17 | /// Whether there are unsaved changes 18 | final bool hasUnsavedChanges; 19 | 20 | /// Whether there's a draft available for the current section 21 | final bool hasDraft; 22 | 23 | /// Whether line breaks should be prevented (for books with links) 24 | final bool preventLineBreaks; 25 | 26 | const EditorState({ 27 | this.isOpen = false, 28 | this.currentIndex, 29 | this.currentSectionId, 30 | this.currentText, 31 | this.hasUnsavedChanges = false, 32 | this.hasDraft = false, 33 | this.preventLineBreaks = false, 34 | }); 35 | 36 | /// Creates a copy with updated values 37 | EditorState copyWith({ 38 | bool? isOpen, 39 | int? currentIndex, 40 | String? currentSectionId, 41 | String? currentText, 42 | bool? hasUnsavedChanges, 43 | bool? hasDraft, 44 | bool? preventLineBreaks, 45 | }) { 46 | return EditorState( 47 | isOpen: isOpen ?? this.isOpen, 48 | currentIndex: currentIndex ?? this.currentIndex, 49 | currentSectionId: currentSectionId ?? this.currentSectionId, 50 | currentText: currentText ?? this.currentText, 51 | hasUnsavedChanges: hasUnsavedChanges ?? this.hasUnsavedChanges, 52 | hasDraft: hasDraft ?? this.hasDraft, 53 | preventLineBreaks: preventLineBreaks ?? this.preventLineBreaks, 54 | ); 55 | } 56 | 57 | @override 58 | List get props => [ 59 | isOpen, 60 | currentIndex, 61 | currentSectionId, 62 | currentText, 63 | hasUnsavedChanges, 64 | hasDraft, 65 | preventLineBreaks, 66 | ]; 67 | } -------------------------------------------------------------------------------- /lib/personal_notes/utils/note_text_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:characters/characters.dart'; 2 | 3 | const int kReferenceWordsLimit = 10; 4 | 5 | final RegExp wordPattern = RegExp(r'[\p{L}\d]+', unicode: true); 6 | final RegExp wordCharPattern = RegExp(r'[\p{L}\d]', unicode: true); 7 | 8 | List splitBookContentIntoLines(String content) { 9 | final normalized = content.replaceAll('\r\n', '\n'); 10 | final lines = normalized.split('\n'); 11 | if (lines.isNotEmpty && lines.last.isEmpty) { 12 | lines.removeLast(); 13 | } 14 | return lines; 15 | } 16 | 17 | List extractReferenceWordsFromLine( 18 | String line, { 19 | int limit = kReferenceWordsLimit, 20 | }) { 21 | final matches = wordPattern.allMatches(line); 22 | final words = []; 23 | for (final match in matches) { 24 | words.add(normalizeWord(match.group(0)!)); 25 | if (words.length == limit) { 26 | break; 27 | } 28 | } 29 | return words; 30 | } 31 | 32 | List extractReferenceWordsFromLines( 33 | List lines, 34 | int lineNumber, { 35 | int limit = kReferenceWordsLimit, 36 | }) { 37 | final index = lineNumber - 1; 38 | if (index < 0 || index >= lines.length) { 39 | return const []; 40 | } 41 | return extractReferenceWordsFromLine(lines[index], limit: limit); 42 | } 43 | 44 | String normalizeWord(String word) { 45 | final cleaned = 46 | word.characters.where((c) => wordCharPattern.hasMatch(c)).toString(); 47 | final normalized = cleaned.trim(); 48 | if (normalized.isEmpty) { 49 | return word.trim(); 50 | } 51 | return normalized; 52 | } 53 | 54 | double computeWordOverlapRatio(List stored, List actual) { 55 | if (stored.isEmpty) { 56 | return 1.0; 57 | } 58 | if (actual.isEmpty) { 59 | return 0.0; 60 | } 61 | final storedSet = stored.map(normalizeWord).toSet(); 62 | final actualSet = actual.map(normalizeWord).toSet(); 63 | if (storedSet.isEmpty) { 64 | return actualSet.isEmpty ? 1.0 : 0.0; 65 | } 66 | 67 | final matches = storedSet.intersection(actualSet).length; 68 | return matches / storedSet.length; 69 | } 70 | -------------------------------------------------------------------------------- /lib/core/window_listener.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:flutter/foundation.dart'; 3 | import 'package:window_manager/window_manager.dart'; 4 | 5 | /// Window listener that handles window events properly to prevent crashes 6 | class AppWindowListener extends WindowListener { 7 | @override 8 | void onWindowClose() { 9 | if (kDebugMode) { 10 | print('Window close requested'); 11 | } 12 | 13 | try { 14 | // Perform cleanup operations here if needed 15 | 16 | // Close the window properly 17 | if (!kIsWeb && 18 | (Platform.isWindows || Platform.isLinux || Platform.isMacOS)) { 19 | // Use Future.microtask to avoid blocking the current execution 20 | Future.microtask(() async { 21 | await windowManager.destroy(); 22 | }); 23 | } 24 | } catch (e) { 25 | if (kDebugMode) { 26 | print('Error during window close: $e'); 27 | } 28 | // Force exit if cleanup fails - but only as last resort 29 | exit(0); 30 | } 31 | } 32 | 33 | @override 34 | void onWindowFocus() { 35 | if (kDebugMode) { 36 | print('Window focused'); 37 | } 38 | } 39 | 40 | @override 41 | void onWindowBlur() { 42 | if (kDebugMode) { 43 | print('Window blurred'); 44 | } 45 | } 46 | 47 | @override 48 | void onWindowMinimize() { 49 | if (kDebugMode) { 50 | print('Window minimized'); 51 | } 52 | } 53 | 54 | @override 55 | void onWindowRestore() { 56 | if (kDebugMode) { 57 | print('Window restored'); 58 | } 59 | } 60 | 61 | @override 62 | void onWindowResize() { 63 | if (kDebugMode) { 64 | print('Window resized'); 65 | } 66 | } 67 | 68 | @override 69 | void onWindowMove() { 70 | if (kDebugMode) { 71 | print('Window moved'); 72 | } 73 | } 74 | 75 | /// Clean up the listener when disposing 76 | void dispose() { 77 | // Remove this listener from window manager 78 | if (!kIsWeb && 79 | (Platform.isWindows || Platform.isLinux || Platform.isMacOS)) { 80 | windowManager.removeListener(this); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | id "dev.flutter.flutter-gradle-plugin" 5 | } 6 | 7 | 8 | def localProperties = new Properties() 9 | def localPropertiesFile = rootProject.file('local.properties') 10 | if (localPropertiesFile.exists()) { 11 | localPropertiesFile.withReader('UTF-8') { reader -> 12 | localProperties.load(reader) 13 | } 14 | } 15 | 16 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 17 | if (flutterVersionCode == null) { 18 | flutterVersionCode = '1' 19 | } 20 | 21 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 22 | if (flutterVersionName == null) { 23 | flutterVersionName = '1.0' 24 | } 25 | 26 | android { 27 | namespace "com.example.otzaria" 28 | compileSdkVersion 35 29 | ndkVersion "27.0.12077973" 30 | 31 | compileOptions { 32 | sourceCompatibility JavaVersion.VERSION_17 33 | targetCompatibility JavaVersion.VERSION_17 34 | } 35 | 36 | kotlinOptions { 37 | jvmTarget = '17' 38 | } 39 | 40 | sourceSets { 41 | main.java.srcDirs += 'src/main/kotlin' 42 | } 43 | 44 | defaultConfig { 45 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 46 | applicationId "com.sivan22.otzaria" 47 | // You can update the following values to match your application needs. 48 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. 49 | minSdkVersion 23 50 | targetSdkVersion 35 51 | versionCode flutterVersionCode.toInteger() 52 | versionName flutterVersionName 53 | } 54 | 55 | 56 | 57 | 58 | buildTypes { 59 | release { 60 | // TODO: Add your own signing config for the release build. 61 | // Signing with the debug keys for now, so `flutter run --release` works. 62 | signingConfig signingConfigs.debug 63 | } 64 | } 65 | } 66 | 67 | flutter { 68 | source '../..' 69 | } 70 | 71 | dependencies {} 72 | -------------------------------------------------------------------------------- /lib/tabs/models/tab.dart: -------------------------------------------------------------------------------- 1 | /* this is a representation of the tabs that could be open in the app. 2 | a tab is either a pdf book or a text book, or a full text search window*/ 3 | 4 | import 'package:otzaria/tabs/models/pdf_tab.dart'; 5 | import 'package:otzaria/tabs/models/searching_tab.dart'; 6 | import 'package:otzaria/tabs/models/text_tab.dart'; 7 | import 'package:otzaria/models/books.dart'; 8 | 9 | abstract class OpenedTab { 10 | String title; 11 | OpenedTab(this.title); 12 | 13 | /// Called when the tab is being disposed. 14 | /// Override this method to perform cleanup. 15 | void dispose() {} 16 | 17 | factory OpenedTab.from(OpenedTab tab) { 18 | if (tab is TextBookTab) { 19 | return TextBookTab( 20 | index: tab.index, 21 | book: tab.book, 22 | searchText: tab.searchText, 23 | commentators: tab.commentators, 24 | ); 25 | } else if (tab is PdfBookTab) { 26 | return PdfBookTab( 27 | book: tab.book, 28 | pageNumber: tab.pageNumber, 29 | ); 30 | } 31 | return tab; 32 | } 33 | 34 | factory OpenedTab.fromBook(Book book, int index, 35 | {String searchText = '', 36 | List? commentators, 37 | bool openLeftPane = false}) { 38 | if (book is PdfBook) { 39 | return PdfBookTab( 40 | book: book, 41 | pageNumber: index, 42 | openLeftPane: openLeftPane, 43 | searchText: searchText, 44 | ); 45 | } else if (book is TextBook) { 46 | return TextBookTab( 47 | book: book, 48 | index: index, 49 | searchText: searchText, 50 | commentators: commentators, 51 | openLeftPane: openLeftPane, 52 | ); 53 | } 54 | throw UnsupportedError("Unsupported book type: ${book.runtimeType}"); 55 | } 56 | 57 | factory OpenedTab.fromJson(Map json) { 58 | String type = json['type']; 59 | if (type == 'TextBookTab') { 60 | return TextBookTab.fromJson(json); 61 | } else if (type == 'PdfBookTab') { 62 | return PdfBookTab.fromJson(json); 63 | } 64 | return SearchingTab.fromJson(json); 65 | } 66 | Map toJson(); 67 | } 68 | -------------------------------------------------------------------------------- /lib/text_book/editing/models/text_draft.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | /// Represents an auto-saved draft of a text section being edited 4 | class TextDraft extends Equatable { 5 | /// Book identifier 6 | final String bookId; 7 | 8 | /// Stable section identifier 9 | final String sectionId; 10 | 11 | /// Markdown content of the draft 12 | final String markdownContent; 13 | 14 | /// When this draft was created/updated 15 | final DateTime timestamp; 16 | 17 | const TextDraft({ 18 | required this.bookId, 19 | required this.sectionId, 20 | required this.markdownContent, 21 | required this.timestamp, 22 | }); 23 | 24 | /// Creates a draft from current editing content 25 | factory TextDraft.create({ 26 | required String bookId, 27 | required String sectionId, 28 | required String markdownContent, 29 | }) { 30 | return TextDraft( 31 | bookId: bookId, 32 | sectionId: sectionId, 33 | markdownContent: markdownContent, 34 | timestamp: DateTime.now(), 35 | ); 36 | } 37 | 38 | /// Creates a draft from file content 39 | factory TextDraft.fromFileContent({ 40 | required String bookId, 41 | required String sectionId, 42 | required String fileContent, 43 | required DateTime fileTimestamp, 44 | }) { 45 | return TextDraft( 46 | bookId: bookId, 47 | sectionId: sectionId, 48 | markdownContent: fileContent, 49 | timestamp: fileTimestamp, 50 | ); 51 | } 52 | 53 | /// Converts the draft to file content (plain markdown, no frontmatter for drafts) 54 | String toFileContent() { 55 | return markdownContent; 56 | } 57 | 58 | /// Creates a copy with updated values 59 | TextDraft copyWith({ 60 | String? bookId, 61 | String? sectionId, 62 | String? markdownContent, 63 | DateTime? timestamp, 64 | }) { 65 | return TextDraft( 66 | bookId: bookId ?? this.bookId, 67 | sectionId: sectionId ?? this.sectionId, 68 | markdownContent: markdownContent ?? this.markdownContent, 69 | timestamp: timestamp ?? this.timestamp, 70 | ); 71 | } 72 | 73 | @override 74 | List get props => [bookId, sectionId, markdownContent, timestamp]; 75 | } -------------------------------------------------------------------------------- /assets/shamor_zachor/data/yerushalmi.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "name": "תלמוד ירושלמי", 4 | "content_type": "דף", 5 | "subcategories": [ 6 | { 7 | "name": "סדר זרעים", 8 | "content_type": "דף", 9 | "books": { 10 | "ברכות": { "pages": 68 }, 11 | "פאה": { "pages": 37 }, 12 | "דמאי": { "pages": 34 }, 13 | "כלאים": { "pages": 44 }, 14 | "שביעית": { "pages": 31 }, 15 | "תרומות": { "pages": 59 }, 16 | "מעשרות": { "pages": 26 }, 17 | "מעשר שני": { "pages": 33 }, 18 | "חלה": { "pages": 28 }, 19 | "ערלה": { "pages": 20 }, 20 | "ביכורים": { "pages": 13 } 21 | } 22 | }, 23 | { 24 | "name": "סדר מועד", 25 | "content_type": "דף", 26 | "books": { 27 | "שבת": { "pages": 92 }, 28 | "עירובין": { "pages": 65 }, 29 | "פסחים": { "pages": 71 }, 30 | "ביצה": { "pages": 22 }, 31 | "ראש השנה": { "pages": 22 }, 32 | "יומא": { "pages": 42 }, 33 | "סוכה": { "pages": 26 }, 34 | "תענית": { "pages": 26 }, 35 | "שקלים": { "pages": 33 }, 36 | "מגילה": { "pages": 34 }, 37 | "חגיגה": { "pages": 22 }, 38 | "מועד קטן": { "pages": 19 } 39 | } 40 | }, 41 | { 42 | "name": "סדר נשים", 43 | "content_type": "דף", 44 | "books": { 45 | "יבמות": { "pages": 85 }, 46 | "כתובות": { "pages": 72 }, 47 | "סוטה": { "pages": 47 }, 48 | "נדרים": { "pages": 40 }, 49 | "נזיר": { "pages": 47 }, 50 | "גיטין": { "pages": 54 }, 51 | "קידושין": { "pages": 48 } 52 | } 53 | }, 54 | { 55 | "name": "סדר נזיקין", 56 | "content_type": "דף", 57 | "books": { 58 | "בבא קמא": { "pages": 44 }, 59 | "בבא מציעא": { "pages": 37 }, 60 | "בבא בתרא": { "pages": 34 }, 61 | "שבועות": { "pages": 44 }, 62 | "מכות": { "pages": 9 }, 63 | "סנהדרין": { "pages": 57 }, 64 | "עבודה זרה": { "pages": 37 }, 65 | "הוריות": { "pages": 19 } 66 | } 67 | }, 68 | { 69 | "name": "סדר טהרות", 70 | "content_type": "דף", 71 | "books": { 72 | "נדה": { "pages": 13 } 73 | } 74 | } 75 | ] 76 | } 77 | -------------------------------------------------------------------------------- /packages/shamor_zachor/assets/data/yerushalmi.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "name": "תלמוד ירושלמי", 4 | "content_type": "דף", 5 | "subcategories": [ 6 | { 7 | "name": "סדר זרעים", 8 | "content_type": "דף", 9 | "books": { 10 | "ברכות": { "pages": 68 }, 11 | "פאה": { "pages": 37 }, 12 | "דמאי": { "pages": 34 }, 13 | "כלאים": { "pages": 44 }, 14 | "שביעית": { "pages": 31 }, 15 | "תרומות": { "pages": 59 }, 16 | "מעשרות": { "pages": 26 }, 17 | "מעשר שני": { "pages": 33 }, 18 | "חלה": { "pages": 28 }, 19 | "ערלה": { "pages": 20 }, 20 | "ביכורים": { "pages": 13 } 21 | } 22 | }, 23 | { 24 | "name": "סדר מועד", 25 | "content_type": "דף", 26 | "books": { 27 | "שבת": { "pages": 92 }, 28 | "עירובין": { "pages": 65 }, 29 | "פסחים": { "pages": 71 }, 30 | "ביצה": { "pages": 22 }, 31 | "ראש השנה": { "pages": 22 }, 32 | "יומא": { "pages": 42 }, 33 | "סוכה": { "pages": 26 }, 34 | "תענית": { "pages": 26 }, 35 | "שקלים": { "pages": 33 }, 36 | "מגילה": { "pages": 34 }, 37 | "חגיגה": { "pages": 22 }, 38 | "מועד קטן": { "pages": 19 } 39 | } 40 | }, 41 | { 42 | "name": "סדר נשים", 43 | "content_type": "דף", 44 | "books": { 45 | "יבמות": { "pages": 85 }, 46 | "כתובות": { "pages": 72 }, 47 | "סוטה": { "pages": 47 }, 48 | "נדרים": { "pages": 40 }, 49 | "נזיר": { "pages": 47 }, 50 | "גיטין": { "pages": 54 }, 51 | "קידושין": { "pages": 48 } 52 | } 53 | }, 54 | { 55 | "name": "סדר נזיקין", 56 | "content_type": "דף", 57 | "books": { 58 | "בבא קמא": { "pages": 44 }, 59 | "בבא מציעא": { "pages": 37 }, 60 | "בבא בתרא": { "pages": 34 }, 61 | "שבועות": { "pages": 44 }, 62 | "מכות": { "pages": 9 }, 63 | "סנהדרין": { "pages": 57 }, 64 | "עבודה זרה": { "pages": 37 }, 65 | "הוריות": { "pages": 19 } 66 | } 67 | }, 68 | { 69 | "name": "סדר טהרות", 70 | "content_type": "דף", 71 | "books": { 72 | "נדה": { "pages": 13 } 73 | } 74 | } 75 | ] 76 | } 77 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CADisableMinimumFrameDurationOnPhone 6 | 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleDisplayName 10 | Otzaria 11 | CFBundleExecutable 12 | $(EXECUTABLE_NAME) 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | otzaria 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | $(FLUTTER_BUILD_NAME) 23 | CFBundleSignature 24 | ???? 25 | CFBundleVersion 26 | $(FLUTTER_BUILD_NUMBER) 27 | LSRequiresIPhoneOS 28 | 29 | LSSupportsOpeningDocumentsInPlace 30 | 31 | UIApplicationSupportsIndirectInputEvents 32 | 33 | UIFileSharingEnabled 34 | 35 | UILaunchStoryboardName 36 | LaunchScreen 37 | UIMainStoryboardFile 38 | Main 39 | NSPhotoLibraryUsageDescription 40 | This app needs access to photos to access and manage the library's book images. 41 | UISupportedInterfaceOrientations 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationLandscapeLeft 45 | UIInterfaceOrientationLandscapeRight 46 | 47 | LSApplicationCategoryType 48 | 49 | UISupportedInterfaceOrientations~ipad 50 | 51 | UIInterfaceOrientationPortrait 52 | UIInterfaceOrientationPortraitUpsideDown 53 | UIInterfaceOrientationLandscapeLeft 54 | UIInterfaceOrientationLandscapeRight 55 | 56 | LSApplicationQueriesSchemes 57 | 58 | mailto 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /lib/search/models/search_terms_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:otzaria/search/utils/regex_patterns.dart'; 2 | 3 | class SearchTerm { 4 | final String word; 5 | final List alternatives; 6 | 7 | SearchTerm({ 8 | required this.word, 9 | this.alternatives = const [], 10 | }); 11 | 12 | SearchTerm copyWith({ 13 | String? word, 14 | List? alternatives, 15 | }) { 16 | return SearchTerm( 17 | word: word ?? this.word, 18 | alternatives: alternatives ?? this.alternatives, 19 | ); 20 | } 21 | 22 | SearchTerm addAlternative(String alternative) { 23 | return copyWith( 24 | alternatives: [...alternatives, alternative], 25 | ); 26 | } 27 | 28 | SearchTerm removeAlternative(int index) { 29 | final newAlternatives = List.from(alternatives); 30 | if (index >= 0 && index < newAlternatives.length) { 31 | newAlternatives.removeAt(index); 32 | } 33 | return copyWith(alternatives: newAlternatives); 34 | } 35 | 36 | String get displayText { 37 | if (alternatives.isEmpty) { 38 | return word; 39 | } 40 | return '$word או ${alternatives.join(' או ')}'; 41 | } 42 | } 43 | 44 | class SearchQuery { 45 | final List terms; 46 | 47 | SearchQuery({this.terms = const []}); 48 | 49 | SearchQuery copyWith({List? terms}) { 50 | return SearchQuery(terms: terms ?? this.terms); 51 | } 52 | 53 | SearchQuery updateTerm(int index, SearchTerm term) { 54 | final newTerms = List.from(terms); 55 | if (index >= 0 && index < newTerms.length) { 56 | newTerms[index] = term; 57 | } 58 | return copyWith(terms: newTerms); 59 | } 60 | 61 | String get displayText { 62 | if (terms.isEmpty) return ''; 63 | return terms.map((term) => term.displayText).join(' ו '); 64 | } 65 | 66 | String get originalQuery { 67 | return terms.map((term) => term.word).join(' '); 68 | } 69 | 70 | static SearchQuery fromString(String query) { 71 | if (query.trim().isEmpty) { 72 | return SearchQuery(); 73 | } 74 | 75 | final words = query.trim().split(SearchRegexPatterns.wordSplitter); 76 | final terms = words.map((word) => SearchTerm(word: word)).toList(); 77 | return SearchQuery(terms: terms); 78 | } 79 | } -------------------------------------------------------------------------------- /installer/otzaria.iss: -------------------------------------------------------------------------------- 1 | ; Script generated by the Inno Setup Script Wizard. 2 | ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! 3 | 4 | #define MyAppName "אוצריא" 5 | #define MyAppVersion "0.9.71" 6 | #define MyAppPublisher "sivan22" 7 | #define MyAppURL "https://github.com/Y-PLONI/otzaria" 8 | #define MyAppExeName "otzaria.exe" 9 | 10 | [Setup] 11 | ; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications. 12 | ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) 13 | AppId={{EEC4F712-CD05-4D15-A753-509E840A51A5} 14 | AppName={#MyAppName} 15 | AppVersion={#MyAppVersion} 16 | ;AppVerName={#MyAppName} {#MyAppVersion} 17 | AppPublisher={#MyAppPublisher} 18 | AppPublisherURL={#MyAppURL} 19 | AppSupportURL={#MyAppURL} 20 | AppUpdatesURL={#MyAppURL} 21 | DefaultDirName=C:\{#MyAppName} 22 | DefaultGroupName={#MyAppName} 23 | DisableProgramGroupPage=yes 24 | ; Uncomment the following line to run in non administrative install mode (install for current user only.) 25 | ;PrivilegesRequired=lowest 26 | OutputDir=.\ 27 | OutputBaseFilename=otzaria-{#MyAppVersion}-windows 28 | SetupIconFile=white_sketch128x128.ico 29 | Compression=lzma 30 | SolidCompression=yes 31 | WizardStyle=modern 32 | DisableDirPage=auto 33 | 34 | [InstallDelete] 35 | Type: filesandordirs; Name: "{app}\default.isar"; 36 | 37 | [Tasks] 38 | Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked 39 | 40 | [Icons] 41 | Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" 42 | Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon 43 | 44 | [Run] 45 | Filename: "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"; WorkingDir: "{app}"; Parameters: " -sta -WindowStyle Hidden -noprofile -executionpolicy bypass -file uninstall_msix.ps1"; 46 | 47 | [Languages] 48 | Name: "hebrew"; MessagesFile: "compiler:Languages\Hebrew.isl" 49 | 50 | [Files] 51 | Source: "..\build\windows\x64\runner\Release\*"; \ 52 | Excludes: "*.msix,*.msixbundle,*.appx,*.appxbundle,*.appinstaller"; \ 53 | DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs 54 | Source: "uninstall_msix.ps1"; DestDir: "{app}"; Flags: ignoreversion 55 | -------------------------------------------------------------------------------- /lib/widgets/filter_list/src/state/filter_state.dart: -------------------------------------------------------------------------------- 1 | import 'provider.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class FilterState extends ListenableState { 5 | FilterState( 6 | {List? allItems, 7 | List? selectedItems, 8 | this.maximumSelectionLength}) { 9 | this.selectedItems = selectedItems; 10 | items = allItems; 11 | } 12 | 13 | static FilterState of(BuildContext context) => 14 | StateProvider.of>(context); 15 | final int? maximumSelectionLength; 16 | 17 | /// List of all items 18 | List? _items; 19 | List? get items => _items; 20 | set items(List? value) { 21 | if (value == _items) { 22 | return; 23 | } else if (value == null) { 24 | _items = []; 25 | } else { 26 | _items = List.from(value); 27 | } 28 | notifyListeners(); 29 | } 30 | 31 | /// List of all selected items 32 | List? _selectedItems; 33 | List? get selectedItems => _selectedItems; 34 | set selectedItems(List? value) { 35 | if (value == selectedItems) { 36 | return; 37 | } else if (value == null) { 38 | _selectedItems = []; 39 | } else { 40 | _selectedItems = List.from(value); 41 | } 42 | 43 | notifyListeners(); 44 | } 45 | 46 | int get selectedItemsCount => selectedItems?.length ?? 0; 47 | 48 | // Add item in to selected list 49 | void addSelectedItem(K item) { 50 | _selectedItems!.add(item); 51 | 52 | notifyListeners(); 53 | } 54 | 55 | // Remove item from selected list 56 | void removeSelectedItem(K item) { 57 | _selectedItems!.remove(item); 58 | 59 | notifyListeners(); 60 | } 61 | 62 | // perform filter operation 63 | void filter(bool Function(K) filter) { 64 | _items = _items!.where(filter).toList(); 65 | notifyListeners(); 66 | } 67 | 68 | // Clear selected list 69 | void clearSelectedList() { 70 | _selectedItems!.clear(); 71 | 72 | notifyListeners(); 73 | } 74 | 75 | @override 76 | bool operator ==(Object other) => 77 | identical(this, other) || 78 | other is FilterState && 79 | runtimeType == other.runtimeType && 80 | _items == other.items && 81 | _selectedItems == other.selectedItems; 82 | 83 | @override 84 | int get hashCode => _items.hashCode ^ _selectedItems.hashCode; 85 | } 86 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 20 | 28 | 32 | 36 | 37 | 38 | 39 | 40 | 41 | 43 | 46 | 47 | -------------------------------------------------------------------------------- /test/models/phone_report_data_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:otzaria/models/phone_report_data.dart'; 3 | 4 | void main() { 5 | group('PhoneReportData', () { 6 | test('should serialize to JSON correctly', () { 7 | final reportData = PhoneReportData( 8 | selectedText: 'Test text', 9 | errorId: 1, 10 | moreInfo: 'Additional info', 11 | libraryVersion: '1.0.0', 12 | bookId: 123, 13 | lineNumber: 456, 14 | ); 15 | 16 | final json = reportData.toJson(); 17 | 18 | expect(json['library_ver'], equals('1.0.0')); 19 | expect(json['book_id'], equals(123)); 20 | expect(json['line'], equals(456)); 21 | expect(json['error_id'], equals(1)); 22 | expect(json['more_info'], equals('Additional info')); 23 | }); 24 | 25 | test('should create copy with updated fields', () { 26 | final original = PhoneReportData( 27 | selectedText: 'Original text', 28 | errorId: 1, 29 | moreInfo: 'Original info', 30 | libraryVersion: '1.0.0', 31 | bookId: 123, 32 | lineNumber: 456, 33 | ); 34 | 35 | final updated = original.copyWith( 36 | errorId: 2, 37 | moreInfo: 'Updated info', 38 | ); 39 | 40 | expect(updated.selectedText, equals('Original text')); 41 | expect(updated.errorId, equals(2)); 42 | expect(updated.moreInfo, equals('Updated info')); 43 | expect(updated.libraryVersion, equals('1.0.0')); 44 | expect(updated.bookId, equals(123)); 45 | expect(updated.lineNumber, equals(456)); 46 | }); 47 | }); 48 | 49 | group('ErrorType', () { 50 | test('should find error type by ID', () { 51 | final errorType = ErrorType.getById(1); 52 | expect(errorType, isNotNull); 53 | expect(errorType!.id, equals(1)); 54 | expect(errorType.hebrewLabel, equals('שגיאת כתיב')); 55 | }); 56 | 57 | test('should return null for invalid ID', () { 58 | final errorType = ErrorType.getById(999); 59 | expect(errorType, isNull); 60 | }); 61 | 62 | test('should have all expected error types', () { 63 | expect(ErrorType.errorTypes.length, equals(6)); 64 | expect(ErrorType.errorTypes[0].hebrewLabel, equals('שגיאת כתיב')); 65 | expect(ErrorType.errorTypes[5].hebrewLabel, equals('אחר')); 66 | }); 67 | }); 68 | } 69 | -------------------------------------------------------------------------------- /lib/search/bloc/search_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:otzaria/models/books.dart'; 2 | import 'package:otzaria/search/models/search_configuration.dart'; 3 | import 'package:search_engine/search_engine.dart'; 4 | 5 | abstract class SearchEvent { 6 | const SearchEvent(); 7 | } 8 | 9 | class UpdateFilterQuery extends SearchEvent { 10 | final String query; 11 | UpdateFilterQuery(this.query); 12 | } 13 | 14 | class ClearFilter extends SearchEvent { 15 | ClearFilter(); 16 | } 17 | 18 | class UpdateSearchQuery extends SearchEvent { 19 | final String query; 20 | final Map? customSpacing; 21 | final Map>? alternativeWords; 22 | final Map>? searchOptions; 23 | UpdateSearchQuery(this.query, 24 | {this.customSpacing, this.alternativeWords, this.searchOptions}); 25 | } 26 | 27 | class UpdateDistance extends SearchEvent { 28 | final int distance; 29 | UpdateDistance(this.distance); 30 | } 31 | 32 | class ToggleSearchMode extends SearchEvent {} 33 | 34 | class SetSearchMode extends SearchEvent { 35 | final SearchMode searchMode; 36 | SetSearchMode(this.searchMode); 37 | } 38 | 39 | class UpdateBooksToSearch extends SearchEvent { 40 | final Set books; 41 | UpdateBooksToSearch(this.books); 42 | } 43 | 44 | class AddFacet extends SearchEvent { 45 | final String facet; 46 | AddFacet(this.facet); 47 | } 48 | 49 | class RemoveFacet extends SearchEvent { 50 | final String facet; 51 | RemoveFacet(this.facet); 52 | } 53 | 54 | class SetFacet extends SearchEvent { 55 | final String facet; 56 | SetFacet(this.facet); 57 | } 58 | 59 | class UpdateSortOrder extends SearchEvent { 60 | final ResultsOrder order; 61 | UpdateSortOrder(this.order); 62 | } 63 | 64 | class UpdateNumResults extends SearchEvent { 65 | final int numResults; 66 | UpdateNumResults(this.numResults); 67 | } 68 | 69 | class ResetSearch extends SearchEvent {} 70 | 71 | // Events חדשים להגדרות רגקס 72 | class ToggleRegex extends SearchEvent {} 73 | 74 | class ToggleCaseSensitive extends SearchEvent {} 75 | 76 | class ToggleMultiline extends SearchEvent {} 77 | 78 | class ToggleDotAll extends SearchEvent {} 79 | 80 | class ToggleUnicode extends SearchEvent {} 81 | 82 | // Event פנימי לעדכון facet counts 83 | class UpdateFacetCounts extends SearchEvent { 84 | final Map facetCounts; 85 | UpdateFacetCounts(this.facetCounts); 86 | } 87 | -------------------------------------------------------------------------------- /linux/packaging/README.md: -------------------------------------------------------------------------------- 1 | # Linux Packaging for Otzaria 2 | 3 | This directory contains configuration files for creating Linux distribution packages (.deb and .rpm) for Otzaria. 4 | 5 | ## Prerequisites 6 | 7 | To build Linux packages, you need: 8 | 9 | ### On Ubuntu/Debian: 10 | ```bash 11 | sudo apt-get install clang cmake git ninja-build pkg-config libgtk-3-dev liblzma-dev libstdc++-12-dev rpm patchelf 12 | ``` 13 | 14 | ### Flutter Distributor: 15 | ```bash 16 | dart pub global activate flutter_distributor 17 | ``` 18 | 19 | ## Configuration Files 20 | 21 | - `deb/make_config.yaml`: Configuration for Debian (.deb) packages 22 | - `rpm/make_config.yaml`: Configuration for RPM (.rpm) packages 23 | 24 | ## Building Packages 25 | 26 | ### Build .deb package: 27 | ```bash 28 | flutter_distributor release --name=prod --jobs=release-linux-deb 29 | ``` 30 | 31 | ### Build .rpm package: 32 | ```bash 33 | flutter_distributor release --name=prod --jobs=release-linux-rpm 34 | ``` 35 | 36 | ### Build both: 37 | ```bash 38 | flutter_distributor release --name=prod 39 | ``` 40 | 41 | ## Installation 42 | 43 | ### .deb package: 44 | ```bash 45 | sudo dpkg -i otzaria_*.deb 46 | # or 47 | sudo apt install ./otzaria_*.deb 48 | ``` 49 | 50 | ### .rpm package: 51 | ```bash 52 | sudo dnf localinstall ./otzaria_*.rpm 53 | # or 54 | sudo rpm -i otzaria_*.rpm 55 | # or 56 | sudo yum localinstall ./otzaria_*.rpm 57 | ``` 58 | 59 | ## GitHub Actions 60 | 61 | The packaging is automated in the GitHub Actions workflow (`.github/workflows/flutter.yml`). The workflow builds: 62 | 63 | 1. Regular Linux bundle (existing functionality) 64 | 2. .deb package for Debian/Ubuntu users 65 | 3. .rpm package for Fedora/RHEL users 66 | 67 | All packages are automatically uploaded as artifacts and can be downloaded from the Actions tab. 68 | 69 | ## Dependencies 70 | 71 | The packages include the following system dependencies: 72 | - libgtk-3-0 / gtk3 73 | - libblkid1 / util-linux 74 | - liblzma5 / xz-libs 75 | 76 | These will be automatically installed when users install your package. 77 | 78 | ## Testing 79 | 80 | To test the setup locally, ensure you have all prerequisites installed and run: 81 | 82 | ```bash 83 | # Test .deb build 84 | flutter_distributor release --name=prod --jobs=release-linux-deb 85 | 86 | # Test .rpm build 87 | flutter_distributor release --name=prod --jobs=release-linux-rpm 88 | ``` 89 | 90 | The generated packages will be in the `dist/` directory. 91 | -------------------------------------------------------------------------------- /lib/find_ref/find_ref_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:otzaria/data/data_providers/tantivy_data_provider.dart'; 2 | import 'package:otzaria/data/repository/data_repository.dart'; 3 | import 'package:otzaria/utils/text_manipulation.dart'; 4 | import 'package:search_engine/search_engine.dart'; 5 | 6 | class FindRefRepository { 7 | final DataRepository dataRepository; 8 | 9 | FindRefRepository({required this.dataRepository}); 10 | 11 | Future> findRefs(String ref) async { 12 | // שלב 1: שלוף יותר תוצאות מהרגיל כדי לפצות על אלו שיסוננו 13 | final rawResults = await TantivyDataProvider.instance 14 | .searchRefs(replaceParaphrases(removeSectionNames(ref)), 300, false); 15 | 16 | // שלב 2: בצע סינון כפילויות (דה-דופליקציה) חכם 17 | final unique = _dedupeRefs(rawResults); 18 | 19 | // שלב 3: החזר עד 100 תוצאות ייחודיות 20 | return unique.length > 100 21 | ? unique.take(100).toList(growable: false) 22 | : unique; 23 | } 24 | 25 | /// מסננת רשימת תוצאות ומשאירה רק את הייחודיות על בסיס מפתח מורכב. 26 | List _dedupeRefs(List results) { 27 | final seen = {}; // סט לשמירת מפתחות שכבר נראו 28 | final out = []; 29 | 30 | for (final r in results) { 31 | // יצירת מפתח ייחודי חכם שמורכב מ-3 חלקים: 32 | 33 | // 1. טקסט ההפניה לאחר נרמול 34 | final refKey = _normalize(r.reference); 35 | 36 | // 2. יעד ההפניה (קובץ ספציפי או שם ספר וסוג) 37 | final file = r.filePath.trim().toLowerCase(); 38 | final title = r.title.trim().toLowerCase(); 39 | final typ = r.isPdf ? 'pdf' : 'txt'; 40 | final dest = file.isNotEmpty ? file : '$title|$typ'; 41 | 42 | // 3. המיקום המדויק בתוך היעד 43 | final seg = _segNum(r.segment); 44 | 45 | // הרכבת המפתח הסופי 46 | final key = '$refKey|$dest|$seg'; 47 | 48 | // הוסף לרשימת הפלט רק אם המפתח לא נראה בעבר 49 | if (seen.add(key)) { 50 | out.add(r); 51 | } 52 | } 53 | return out; 54 | } 55 | 56 | /// פונקציית עזר לנרמול טקסט: מורידה רווחים, הופכת לאותיות קטנות ומאחדת רווחים. 57 | String _normalize(String? s) => 58 | (s ?? '').trim().toLowerCase().replaceAll(RegExp(r'\s+'), ' '); 59 | 60 | /// פונקציית עזר להמרת 'segment' למספר שלם (int) בצורה בטוחה. 61 | int _segNum(dynamic s) { 62 | if (s is num) return s.round(); 63 | return int.tryParse(s?.toString() ?? '') ?? 0; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /test/unit/mocks/mock_settings_wrapper.mocks.dart: -------------------------------------------------------------------------------- 1 | // Mocks generated by Mockito 5.4.4 from annotations 2 | // in otzaria/test/unit/mocks/mock_settings_wrapper.dart. 3 | // Do not manually edit this file. 4 | 5 | // ignore_for_file: no_leading_underscores_for_library_prefixes 6 | import 'dart:async' as _i4; 7 | 8 | import 'package:mockito/mockito.dart' as _i1; 9 | import 'package:mockito/src/dummies.dart' as _i3; 10 | import 'package:otzaria/utils/settings_wrapper.dart' as _i2; 11 | 12 | // ignore_for_file: type=lint 13 | // ignore_for_file: avoid_redundant_argument_values 14 | // ignore_for_file: avoid_setters_without_getters 15 | // ignore_for_file: comment_references 16 | // ignore_for_file: deprecated_member_use 17 | // ignore_for_file: deprecated_member_use_from_same_package 18 | // ignore_for_file: implementation_imports 19 | // ignore_for_file: invalid_use_of_visible_for_testing_member 20 | // ignore_for_file: prefer_const_constructors 21 | // ignore_for_file: unnecessary_parenthesis 22 | // ignore_for_file: camel_case_types 23 | // ignore_for_file: subtype_of_sealed_class 24 | 25 | /// A class which mocks [SettingsWrapper]. 26 | /// 27 | /// See the documentation for Mockito's code generation for more information. 28 | class MockSettingsWrapper extends _i1.Mock implements _i2.SettingsWrapper { 29 | @override 30 | T getValue( 31 | String? key, { 32 | required T? defaultValue, 33 | }) => 34 | (super.noSuchMethod( 35 | Invocation.method( 36 | #getValue, 37 | [key], 38 | {#defaultValue: defaultValue}, 39 | ), 40 | returnValue: _i3.dummyValue( 41 | this, 42 | Invocation.method( 43 | #getValue, 44 | [key], 45 | {#defaultValue: defaultValue}, 46 | ), 47 | ), 48 | returnValueForMissingStub: _i3.dummyValue( 49 | this, 50 | Invocation.method( 51 | #getValue, 52 | [key], 53 | {#defaultValue: defaultValue}, 54 | ), 55 | ), 56 | ) as T); 57 | 58 | @override 59 | _i4.Future setValue( 60 | String? key, 61 | T? value, 62 | ) => 63 | (super.noSuchMethod( 64 | Invocation.method( 65 | #setValue, 66 | [ 67 | key, 68 | value, 69 | ], 70 | ), 71 | returnValue: _i4.Future.value(), 72 | returnValueForMissingStub: _i4.Future.value(), 73 | ) as _i4.Future); 74 | } 75 | --------------------------------------------------------------------------------