From 84e4c1e9404d096de7e9e387829e650e59a8d1c4 Mon Sep 17 00:00:00 2001 From: Aleksandr Zhukov Date: Tue, 2 Jun 2026 19:34:44 +0200 Subject: [PATCH 01/65] chore: update migrate-to-viewbinding skill with lessons learned - Add step 2.3a: remove tools:viewBindingIgnore from layout XML - Add step 2.3b: check layouts for viewBindingIgnore and use .root - Update general migration steps to include layout XML fix as step 1 - Add viewBindingIgnore check to per-file verification checklist - Update 'Unresolved reference' error docs with viewBindingIgnore as common cause - Update included layouts pitfall with .root pattern for delegates - Move migration tracker to docs/ (gitignored) to avoid branch conflicts Co-Authored-By: Claude Opus 4.8 --- .../skills/migrate-to-viewbinding/SKILL.md | 390 +++++++++++++++ .../references/library-migration.md | 205 ++++++++ .../references/patterns.md | 451 ++++++++++++++++++ .../references/testing-guide.md | 156 ++++++ 4 files changed, 1202 insertions(+) create mode 100644 .claude/skills/migrate-to-viewbinding/SKILL.md create mode 100644 .claude/skills/migrate-to-viewbinding/references/library-migration.md create mode 100644 .claude/skills/migrate-to-viewbinding/references/patterns.md create mode 100644 .claude/skills/migrate-to-viewbinding/references/testing-guide.md diff --git a/.claude/skills/migrate-to-viewbinding/SKILL.md b/.claude/skills/migrate-to-viewbinding/SKILL.md new file mode 100644 index 0000000000..b0242cee8e --- /dev/null +++ b/.claude/skills/migrate-to-viewbinding/SKILL.md @@ -0,0 +1,390 @@ +--- +name: migrate-to-viewbinding +description: This skill should be used when the user asks to "migrate from Kotlin synthetic to ViewBinding", "migrate Kotlin Android Extensions", "remove kotlinx.android.synthetic", "convert synthetic to view binding", "replace synthetic imports", "migrate to view binding", "analyze synthetic usage", or "get rid of synthetic". Performs systematic migration of Kotlin Synthetic view references to Jetpack ViewBinding in the stepik-android project using the by.kirich1409.viewbindingdelegate library. +version: 1.0.0 +--- + +# Migrate from Kotlin Synthetic to ViewBinding + +## Overview + +Guide the systematic migration of Kotlin Android Extensions (synthetic) view references to Jetpack ViewBinding in the stepik-android project. The migration follows a three-phase approach: analyze the codebase and create a tracking file, iteratively migrate each file, and optionally upgrade the ViewBindingPropertyDelegate library. + +**Constraints:** + +- Migrate ONLY files using `kotlinx.android.synthetic.*` imports +- Do NOT migrate files using `findViewById` directly (legacy Java or Kotlin) +- Do NOT touch files that already use ViewBinding +- Use the existing `by.kirich1409.viewbindingdelegate` library (v1.4.7, no-reflection variant) +- Follow the project's established ViewBinding naming conventions + +**Project context:** + +- Kotlin 1.7.10, AGP 8.6.1, viewBinding enabled in `app/build.gradle` (lines 153-155) +- ViewBindingPropertyDelegate: `com.github.kirich1409:viewbindingpropertydelegate-noreflection:1.4.7` +- Package: `by.kirich1409.viewbindingdelegate` +- Binding classes generated in `org.stepic.droid.databinding` package +- ~201 files with synthetic imports, ~22 files already using viewBinding +- Existing project examples to reference: DebugFragment, RubricatorActivity, CoursePurchaseBottomSheetDialogFragment, CourseNewsAdapterDelegate + +## Phase 1: Analysis and Tracking + +### Step 1: Scan the project + +Run a comprehensive scan for all Kotlin files importing synthetic: + +```bash +grep -rn "kotlinx.android.synthetic" --include="*.kt" -l | sort +``` + +For each discovered file, extract: + +- **File type**: Activity, Fragment, DialogFragment, BottomSheetDialogFragment, Adapter/ViewHolder, or utility/helper +- **Layout file**: Derived from the synthetic import (e.g., `kotlinx.android.synthetic.main.activity_auth_social.*` → layout `activity_auth_social`) +- **Binding class**: PascalCase of layout + "Binding" (e.g., `ActivityAuthSocialBinding`) +- **Migration needed**: Confirm the file actually uses synthetic view references (not just type imports) + +### Step 2: Create the migration tracker + +Create `docs/migrate-to-viewbinding/MIGRATION_TRACKER.md` with the following exact format: + +> **Note:** The `docs/` directory is listed in `.gitignore` — this tracker file will NOT be committed to git and will not conflict with the APPS-3860 branch. + +```markdown +# Kotlin Synthetic → ViewBinding Migration Tracker + +> Generated: YYYY-MM-DD +> Project: stepik-android + +## Progress Summary + +| Metric | Count | +|--------|-------| +| Total files with synthetic imports | N | +| Needs migration | N | +| Skipped (no direct usage / non-UI) | N | +| Completed | N | +| Errors / Blocked | N | + +## Migration Queue + +| # | File Path | Type | Layout | Binding Class | Status | Comments | +|---|-----------|------|--------|---------------|--------|----------| +| 1 | `app/src/main/java/...` | Activity | `activity_xxx` | `ActivityXxxBinding` | ⏳ Pending | | +``` + +**Status values** (use emoji markers for quick scanning): + +- `⏳ Pending` — needs migration, not started +- `🔄 In Progress` — actively being migrated +- `✅ Done` — migration complete +- `⏭️ Skip` — does not need migration (verify before marking) +- `⚠️ Blocked` — error or dependency issue + +Update the progress summary counts whenever a status changes. + +### Step 3: Present the analysis to the user + +After creating the tracking file, show the user: +- Total count of files needing migration +- Distribution by type (Activities, Fragments, Dialogs, Adapters, etc.) +- Any files flagged as potential skips (no direct synthetic usage, utility classes) +- Ask the user to confirm before starting the migration cycle + +## Phase 2: Migration Cycle + +Process each file marked `⏳ Pending` sequentially. For each file: + +### 2.1: Mark as In Progress + +Update the tracking file status to `🔄 In Progress`. + +### 2.2: Read and analyze the file + +Read the target file completely. Identify: + +- All synthetic import lines (including wildcard `.*` and specific view imports like `kotlinx.android.synthetic.main.layout.viewName`) +- Every synthetic view reference in the code body +- The class hierarchy (determines which pattern to use — Activity, Fragment, DialogFragment, BottomSheetDialogFragment, Adapter, utility) +- Any complex patterns (view references in lambdas, companion objects, extension functions, or delegated properties) +- Whether the file imports from multiple layouts (requires multiple binding classes or includes) + +### 2.3: Determine binding class and layout + +Extract the binding class name from the synthetic import: + +``` +import kotlinx.android.synthetic.main.activity_auth_social.* +→ Layout: activity_auth_social +→ Binding class: ActivityAuthSocialBinding +→ Import: org.stepic.droid.databinding.ActivityAuthSocialBinding +``` + +Verify the layout file exists at `app/src/main/res/layout/.xml` (or in the appropriate source set). If the layout file does not exist, the synthetic import was invalid — mark the file as `⏭️ Skip`. + +### 2.3a: Remove `tools:viewBindingIgnore` from layout XML + +**CRITICAL STEP:** Many layout XML files in this project have `tools:viewBindingIgnore="true"` on the root element. This attribute was added when the project used synthetic views and tells the Android build system to **skip generating** ViewBinding classes for that layout. Without removing it, the binding class **will not be generated** and the migration will fail with "Unresolved reference" errors. + +For each layout file referenced by the Kotlin file being migrated: + +1. Read the layout XML file +2. Check if the root element has `tools:viewBindingIgnore="true"` +3. If present, remove it (but keep the `xmlns:tools` namespace if other `tools:` attributes exist in the file) +4. If `xmlns:tools="http://schemas.android.com/tools"` is only used by `viewBindingIgnore` and no other `tools:` attributes remain, remove the namespace declaration too + +**Before:** +```xml + +``` + +**After (if other tools: attributes exist):** +```xml + +``` + +**After (if no other tools: attributes):** +```xml + +``` + +> **Important:** If the Kotlin file imports from multiple layouts (e.g., `import kotlinx.android.synthetic.main.fragment_lesson.*` and `import kotlinx.android.synthetic.main.layout_video_controls.*`), check and fix **each** layout file. + +### 2.3b: Check `` layouts for `viewBindingIgnore` and handle `.root` access + +**CRITICAL STEP:** When a layout uses ``, the included layout may also have `tools:viewBindingIgnore="true"`. If the code accesses views from the included layout (either directly or via a delegate/helper class that takes the include's root `View`), you must: + +1. **Check all ``d layouts** in the parent layout XML for `tools:viewBindingIgnore="true"` +2. **Remove it** from each included layout that is referenced by the migrated code +3. **Use `.root` when passing an included layout to a delegate** that expects a `View` parameter + +**How `` works with ViewBinding:** + +When a parent layout includes another layout: +```xml + + + + + + +``` + +The parent binding exposes the included layout via its own binding class: +- `binding.achievementTile` → type is `ViewAchievementTileBinding` (the included layout's binding) +- `binding.achievementTile.root` → type is `View` (the root view of the included layout) +- `binding.achievementTitle` → direct child views are accessible normally + +**When to use `.root`:** +- When passing the included layout to a delegate/helper that takes a `View` parameter: + ```kotlin + // Delegate expects a View (the include's root) + private val tileDelegate = AchievementTileDelegate(binding.achievementTile.root, resolver) + ``` +- When a helper class uses synthetic imports on the included layout (e.g., `root.achievementLevels`), pass `.root` not the binding itself + +**When NOT to use `.root`:** +- When accessing specific views within the included layout directly through its binding: + ```kotlin + binding.achievementTile.achievementLevels.progress = 5 + ``` + +**Before migration (synthetic):** +```kotlin +import kotlinx.android.synthetic.main.view_achievement_item.view.* +import kotlinx.android.synthetic.main.view_achievement_tile.view.* + +// root.achievementTile is the include's root View (synthetic generates it as a View) +// root.achievementTitle is a direct child +val tileDelegate = AchievementTileDelegate(root.achievementTile, resolver) +``` + +**After migration (viewBinding):** +```kotlin +// binding.achievementTile is ViewAchievementTileBinding (not View) +// binding.achievementTile.root is the include's root View (equivalent to synthetic root.achievementTile) +// binding.achievementTitle is a direct child (same as before) +val tileDelegate = AchievementTileDelegate(binding.achievementTile.root, resolver) +``` + +> **Important:** Scan the entire layout XML for all `` tags. For each included layout, check if `tools:viewBindingIgnore="true"` exists and remove it if the migrated code (or any delegate/helper it uses) references views from that included layout. + +### 2.4: Apply the migration pattern + +Follow the pattern matching the class type. See **`references/patterns.md`** for detailed patterns with before/after examples for each type: Activity, Fragment, DialogFragment, BottomSheetDialogFragment, Adapter/ViewHolder, and utility classes. + +**General migration steps:** + +1. Remove `tools:viewBindingIgnore="true"` from the layout XML root element (see step 2.3a) +2. Remove all `kotlinx.android.synthetic.*` import lines +3. Add `import by.kirich1409.viewbindingdelegate.viewBinding` +4. Add import for the generated binding class (`org.stepic.droid.databinding.`) +5. Declare the binding property: `private val Binding: by viewBinding(::bind)` +6. Replace all synthetic view references: `viewId` → `binding.viewId` +7. Convert snake_case view IDs to camelCase: `root_view` → `rootView`, `sign_in_button` → `signInButton` +8. Preserve existing layout inflation (`setContentView` for Activities, `onCreateView` return for Fragments) + +**Naming conventions in the project:** + +- Binding property names use a descriptive prefix: `debugBinding`, `courseNewsBinding`, `rubricatorBinding`, `filterRootBinding` +- For ViewHolders, use `viewBinding` as the property name +- The prefix typically matches the feature or screen name, not just the class name + +### 2.5: Handle edge cases + +Before finalizing, check for these common edge cases: + +- **Views accessed via `this.viewId`** — some files use `this` qualifier; replace with `binding.viewId` +- **Views accessed via `view?.viewId`** — nullable access in Fragments; use `binding.viewId` (non-null after onViewCreated) +- **Views accessed in `onCreate` before `setContentView`** — move access after layout inflation, or use `findViewById` temporarily +- **Views passed to other classes/functions** — pass `binding.viewId` instead of the bare view reference +- **Extension functions on views** — these still work; just call on `binding.viewId` instead +- **Multiple synthetic imports from different layouts** — check if layouts are ``d; if so, one binding may cover all views. Otherwise, create separate binding properties. + +### 2.6: Verify compilation + +```bash +./gradlew :app:compileDebugKotlin 2>&1 | tail -50 +``` + +If compilation fails, analyze the error and fix before proceeding. See **`references/testing-guide.md`** for common errors and their fixes. + +### 2.7: Update tracking file + +Set status to `✅ Done`. Add any notable comments: + +- Non-standard patterns encountered +- Files that required special handling +- Related files that may need attention +- Any warnings or concerns + +Example tracking file entry after completion: + +```markdown +| 1 | `app/src/.../SocialAuthActivity.kt` | Activity | `activity_auth_social` | `ActivityAuthSocialBinding` | ✅ Done | 14 view references migrated. Uses SmartLockActivityBase. | +``` + +### 2.8: Proceed to next file + +Continue until all `⏳ Pending` files are processed. + +### 2.9: Commit strategy + +Recommend committing after each file or batch of 5-10 migrated files. Use the commit message format: + +``` +refactor: migrate from synthetic to viewBinding +``` + +For batch commits: + +``` +refactor: migrate N files from synthetic to viewBinding + +- File1.kt +- File2.kt +- ... +``` + +## Phase 3: Library Upgrade Offer + +After all migrations are complete, offer the user the option to upgrade the ViewBindingPropertyDelegate library. See **`references/library-migration.md`** for full details. + +**Key constraint:** The new library v2.0.4 requires Kotlin 2.1.10+. The current project uses Kotlin 1.7.10. The library upgrade is NOT compatible without first upgrading Kotlin. Inform the user of this incompatibility and the required steps. + +**Summary of v2.x changes:** + +- New Maven coordinates: `dev.androidbroadcast.vbpd:vbpd:2.0.4` +- New package: `dev.androidbroadcast.vbpd` +- Import change: `import dev.androidbroadcast.vbpd.viewBinding` +- The delegate usage pattern (`viewBinding(XxxBinding::bind)`) remains identical +- Breaking changes: `onViewDestroyed` callback removed, `strictMode` removed +- Minimum Kotlin 2.1.10, minimum AGP 8.8.0 + +## Working Examples in the Project + +Reference these existing viewBinding implementations in the project as gold-standard examples: + +**Fragment:** `app/src/main/java/org/stepik/android/view/course_news/ui/fragment/CourseNewsFragment.kt` +- Uses `private val courseNewsBinding: FragmentCourseNewsBinding by viewBinding(FragmentCourseNewsBinding::bind)` +- Demonstrates binding usage in `onViewCreated`, `ViewStateDelegate`, and `render` method + +**Activity:** `app/src/main/java/org/stepik/android/view/rubricator/ui/RubricatorActivity.kt` +- Uses `private val rubricatorBinding: ActivityRubricatorBinding by viewBinding(ActivityRubricatorBinding::bind)` +- Demonstrates nested binding access: `rubricatorBinding.rubricatorAppbar.viewCenteredToolbarBinding.centeredToolbar` +- Keeps `setContentView(R.layout.activity_rubricator)` in `onCreate` + +**BottomSheetDialogFragment:** `app/src/main/java/org/stepik/android/view/course_purchase/ui/dialog/CoursePurchaseBottomSheetDialogFragment.kt` +- Uses `private val coursePurchaseBinding: BottomSheetDialogCoursePurchaseBinding by viewBinding(BottomSheetDialogCoursePurchaseBinding::bind)` +- Demonstrates passing binding to delegate classes + +**Adapter ViewHolder:** `app/src/main/java/org/stepik/android/view/course_news/ui/adapter/delegate/CourseNewsAdapterDelegate.kt` +- Uses `private val viewBinding: ItemCourseNewsBinding by viewBinding { ItemCourseNewsBinding.bind(root) }` +- Demonstrates binding inside adapter delegate ViewHolder + +## Quick Reference + +### Binding declaration by type + +**The `viewBinding()` delegate uses the SAME import and SAME syntax for all lifecycle-aware component types** (Activity, Fragment, DialogFragment, BottomSheetDialogFragment). The library defines separate extension functions per receiver type, resolved by Kotlin at the call site. The only exception is ViewHolder which uses the lambda form. + +``` +Fragment/Activity/DialogFragment/BottomSheetDialog (all the same): + private val binding: XxxBinding by viewBinding(XxxBinding::bind) + +Adapter ViewHolder (lambda form — no lifecycle to auto-bind): + private val viewBinding: ItemXxxBinding by viewBinding { ItemXxxBinding.bind(root) } +``` + +### Import changes (same for ALL component types) + +``` +REMOVE: import kotlinx.android.synthetic.main.layout_name.* +ADD: import by.kirich1409.viewbindingdelegate.viewBinding ← one import for everything +ADD: import org.stepic.droid.databinding. +``` + +> **Note:** A deprecated `dialogViewBinding()` exists in v1.4.7 (separate import `dialogViewBinding`) that delegates to `viewBinding()`. Never use it — always use `viewBinding()` for all types. + +### View ID naming conversion + +``` +XML ID: android:id="@+id/sign_in_button" +Synthetic: signInButton (accessible as top-level property) +Binding: binding.signInButton (accessible via binding instance) +``` + +### Determining if a file needs migration + +| Scenario | Action | +|----------|--------| +| Has `import kotlinx.android.synthetic.*` | ✅ Needs migration | +| Uses `findViewById` only (no synthetic import) | ⏭️ Skip — not in scope | +| Already has `by.kirich1409.viewbindingdelegate` import | ⏭️ Skip — already migrated | +| Java file (`.java`) with `findViewById` | ⏭️ Skip — not in scope | +| Has synthetic import but no view references | ⏭️ Skip — remove unused import only | + +## Additional Resources + +### Reference Files + +- **`references/patterns.md`** — Detailed migration patterns with before/after examples for Activity, Fragment, DialogFragment, BottomSheetDialogFragment, Adapter/ViewHolder, and utility classes +- **`references/library-migration.md`** — ViewBindingPropertyDelegate v1.x → v2.x upgrade guide with compatibility matrix and step-by-step migration instructions +- **`references/testing-guide.md`** — Compilation verification, common errors and fixes, test commands, and manual verification checklist diff --git a/.claude/skills/migrate-to-viewbinding/references/library-migration.md b/.claude/skills/migrate-to-viewbinding/references/library-migration.md new file mode 100644 index 0000000000..03ad42bac7 --- /dev/null +++ b/.claude/skills/migrate-to-viewbinding/references/library-migration.md @@ -0,0 +1,205 @@ +# ViewBindingPropertyDelegate Library Migration Guide + +> **⚠️ WARNING: Do NOT execute this library upgrade as part of the synthetic-to-ViewBinding migration.** +> The v2.x library requires Kotlin 2.1.10+ (project currently uses 1.7.10). +> This guide is provided for future reference only. See SKILL.md Phase 3 for context. + +## Current Setup + +| Property | Value | +|----------|-------| +| Library | viewbindingpropertydelegate-noreflection | +| Version | 1.4.7 | +| Maven coordinates | `com.github.kirich1409:viewbindingpropertydelegate-noreflection:1.4.7` | +| Package | `by.kirich1409.viewbindingdelegate` | +| Import statement | `import by.kirich1409.viewbindingdelegate.viewBinding` | +| Declared in | `dependencies.gradle` line 51 (version) and line 177 (coordinates) | +| Kotlin version in project | 1.7.10 | +| AGP version in project | 8.6.1 | + +## New Version (v2.x) + +| Property | Value | +|----------|-------| +| Library | vbpd (no-reflection variant) | +| Latest version | 2.0.4 | +| Maven coordinates | `dev.androidbroadcast.vbpd:vbpd:2.0.4` | +| Package | `dev.androidbroadcast.vbpd` | +| Import statement | `import dev.androidbroadcast.vbpd.viewBinding` | +| Minimum Kotlin | 2.1.10 | +| Minimum AGP | 8.8.0 | +| JVM target | 11 | + +Reflection variant also available: `dev.androidbroadcast.vbpd:vbpd-reflection:2.0.4` + +--- + +## Compatibility Assessment + +**⚠️ NOT COMPATIBLE with the current project configuration.** + +| Requirement | Current | Required | Status | +|-------------|---------|----------|--------| +| Kotlin | 1.7.10 | 2.1.10+ | ❌ Major upgrade needed | +| AGP | 8.6.1 | 8.8.0+ | ❌ Minor upgrade needed | +| JVM target | 8 (likely) | 11 | ❌ Configuration change needed | +| Gradle | Unknown | Compatible with AGP 8.8 | ❌ May need upgrade | + +### Impact of Kotlin 1.7.10 → 2.1.10 upgrade + +This is a major version jump spanning multiple breaking changes: + +- Kotlin 1.8: stabilized language features, default gradual mode changes +- Kotlin 1.9: new K2 compiler preview, data object, enum entries +- Kotlin 2.0: K2 compiler becomes default, significant compilation changes +- Kotlin 2.1: further K2 optimizations and deprecations + +The Kotlin upgrade is a separate, substantial effort that should NOT be bundled with the ViewBinding migration. + +--- + +## Migration Steps (After Kotlin Upgrade) + +Perform these steps ONLY after upgrading Kotlin to 2.1.10+ and AGP to 8.8.0+. + +### Step 1: Update dependency in `dependencies.gradle` + +```gradle +ext.versions = [ + // ... + viewBindingDelegate: '2.0.4', // was '1.4.7' +] + +ext.libraries = [ + // ... + // REMOVE: + // viewBindingDelegate: "com.github.kirich1409:viewbindingpropertydelegate-noreflection:$versions.viewBindingDelegate", + // ADD: + viewBindingDelegate: "dev.androidbroadcast.vbpd:vbpd:$versions.viewBindingDelegate", +] +``` + +### Step 2: Replace all imports across the project + +Find all files using the old import: + +```bash +grep -rn "by.kirich1409.viewbindingdelegate" --include="*.kt" -l +``` + +In each file, replace: + +```kotlin +// Old: +import by.kirich1409.viewbindingdelegate.viewBinding + +// New: +import dev.androidbroadcast.vbpd.viewBinding +``` + +No other code changes needed for the basic usage pattern (`viewBinding(XxxBinding::bind)`). + +### Step 3: Address breaking API changes + +#### 3a. `onViewDestroyed` callback removed + +If any binding delegate uses: + +```kotlin +// v1.x pattern (now removed): +private val binding: XxxBinding by viewBinding(XxxBinding::bind) { + onViewDestroyed { vb -> + // cleanup code + } +} +``` + +Move the cleanup code to the appropriate lifecycle method: + +```kotlin +// v2.x — cleanup in lifecycle callbacks: +override fun onDestroyView() { + // cleanup code here + super.onDestroyView() +} +``` + +#### 3b. `ViewBindingPropertyDelegate.strictMode` removed + +Remove any references to strict mode: + +```kotlin +// REMOVE: +ViewBindingPropertyDelegate.strictMode = true +``` + +#### 3c. Lifecycle dependency removed + +The v2.x library no longer depends on `androidx.lifecycle`. It uses: +- `FragmentManager.FragmentLifecycleCallbacks` for fragments +- `Application.ActivityLifecycleCallbacks` for activities + +No code changes needed unless the project manually depended on the library's transitive lifecycle dependency. + +### Step 4: Three Delegate Approaches in v2.x + +The new version offers multiple approaches per target type. The project's current pattern maps directly to **Approach A**: + +#### Approach A — Bind function reference (recommended, same as current) + +```kotlin +import dev.androidbroadcast.vbpd.viewBinding + +// Fragment +private val binding: FragmentXxxBinding by viewBinding(FragmentXxxBinding::bind) + +// Activity +private val binding: ActivityXxxBinding by viewBinding(ActivityXxxBinding::bind) + +// ViewHolder +private val viewBinding: ItemXxxBinding by viewBinding { ItemXxxBinding.bind(root) } +``` + +#### Approach B — Bind function + custom view provider + +```kotlin +private val binding: ActivityMainBinding by viewBinding( + vbFactory = ActivityMainBinding::bind, + viewProvider = { activity -> activity.findViewById(R.id.coordinator) } +) +``` + +#### Approach C — Bind function + root view ID + +```kotlin +private val binding: ActivityMainBinding by viewBinding( + vbFactory = ActivityMainBinding::bind, + viewBindingRootId = R.id.coordinator +) +``` + +### Step 5: Reflection variant (alternative) + +If the project prefers using reflection (less explicit code), the `vbpd-reflection` artifact supports: + +```kotlin +import dev.androidbroadcast.vbpd.viewBinding + +// Reified type — no ::bind needed +private val binding: FragmentXxxBinding by viewBinding() +``` + +This is not recommended for the initial migration since the no-reflection variant provides compile-time safety. + +--- + +## Summary + +| Action | When | Scope | +|--------|------|-------| +| Synthetic → ViewBinding migration | Now | ~201 files | +| Kotlin upgrade | Separate effort | Entire project | +| Library v1.x → v2.x | After Kotlin upgrade | ~22 files with existing viewBinding | +| Import replacement | After library upgrade | Batch find-and-replace | + +**Recommendation:** Complete the synthetic migration first using v1.4.7. The library upgrade is a separate, lower-priority task that depends on a Kotlin version upgrade. diff --git a/.claude/skills/migrate-to-viewbinding/references/patterns.md b/.claude/skills/migrate-to-viewbinding/references/patterns.md new file mode 100644 index 0000000000..c870c99f15 --- /dev/null +++ b/.claude/skills/migrate-to-viewbinding/references/patterns.md @@ -0,0 +1,451 @@ +# ViewBinding Migration Patterns + +## Important: Single Import for All Component Types + +**The `viewBinding()` delegate uses the SAME import for Activity, Fragment, DialogFragment, BottomSheetDialogFragment, ViewHolder, and ViewGroup.** The library defines separate extension functions on each receiver type, but Kotlin resolves the correct one at the call site based on the class where the property is declared. From the consumer's perspective, it's one function name with one import. + +```kotlin +// This ONE import works for ALL component types: +import by.kirich1409.viewbindingdelegate.viewBinding +``` + +**The syntax is also identical** for all lifecycle-aware components (Activity, Fragment, DialogFragment, BottomSheetDialogFragment): + +```kotlin +private val binding: XxxBinding by viewBinding(XxxBinding::bind) +``` + +The library's `Fragment.viewBinding()` extension internally detects `DialogFragment` and dispatches to a dialog-lifecycle-aware delegate that binds/unbinds with the dialog view. + +**The only exception** is ViewHolder/ViewGroup, which uses the lambda form because there is no standard lifecycle to bind to: + +```kotlin +// ViewHolder uses lambda form: +private val viewBinding: ItemXxxBinding by viewBinding { ItemXxxBinding.bind(root) } +``` + +A deprecated `dialogViewBinding()` function exists in v1.4.7 (separate import: `by.kirich1409.viewbindingdelegate.dialogViewBinding`) but simply delegates to `viewBinding()`. **Never use `dialogViewBinding()`** — always use `viewBinding()` for all types. + +What differs between component types is only the **surrounding lifecycle methods** (how the layout gets inflated), not the delegate API. The patterns below show these lifecycle differences. + +--- + +## Binding Class Name Convention + +The generated binding class name is derived from the layout XML filename by converting to PascalCase and appending "Binding": + +| Layout XML | Binding Class | +|------------|--------------| +| `activity_auth_social.xml` | `ActivityAuthSocialBinding` | +| `fragment_debug.xml` | `FragmentDebugBinding` | +| `bottom_sheet_dialog_course_purchase.xml` | `BottomSheetDialogCoursePurchaseBinding` | +| `item_course_news.xml` | `ItemCourseNewsBinding` | +| `dialog_custom.xml` | `DialogCustomBinding` | +| `layout_notification_settings.xml` | `LayoutNotificationSettingsBinding` | + +All binding classes live in the `org.stepic.droid.databinding` package. + +--- + +## Activity Migration Pattern + +### Before (Kotlin Synthetic) + +```kotlin +package org.stepik.android.view.auth.ui.activity + +import kotlinx.android.synthetic.main.activity_auth_social.* +// ... other imports + +class SocialAuthActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_auth_social) + + dismissButton.setOnClickListener { onBackPressed() } + dismissButton.isVisible = true + launchSignUpButton.setOnClickListener { /* ... */ } + signInWithEmail.setOnClickListener { /* ... */ } + signInText.text = spannableSignIn + socialListRecyclerView.layoutManager = GridLayoutManager(this, 3) + root_view.snackbar(messageRes = R.string.connectionProblems) + } +} +``` + +### After (ViewBinding) + +```kotlin +package org.stepik.android.view.auth.ui.activity + +import by.kirich1409.viewbindingdelegate.viewBinding +import org.stepic.droid.databinding.ActivityAuthSocialBinding +// ... other imports (no kotlinx.android.synthetic) + +class SocialAuthActivity : AppCompatActivity() { + + private val binding: ActivityAuthSocialBinding by viewBinding(ActivityAuthSocialBinding::bind) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_auth_social) + + binding.dismissButton.setOnClickListener { onBackPressed() } + binding.dismissButton.isVisible = true + binding.launchSignUpButton.setOnClickListener { /* ... */ } + binding.signInWithEmail.setOnClickListener { /* ... */ } + binding.signInText.text = spannableSignIn + binding.socialListRecyclerView.layoutManager = GridLayoutManager(this, 3) + binding.rootView.snackbar(messageRes = R.string.connectionProblems) + } +} +``` + +### Key Points + +- **Keep `setContentView(R.layout.xxx)`** — the delegate auto-binds to the content view +- **View ID conversion**: snake_case → camelCase (`root_view` → `rootView`, `sign_in_button` → `signInButton`) +- **Binding naming convention**: Use a descriptive prefix + "Binding" (e.g., `rubricatorBinding`, `courseNewsBinding`, `socialAuthBinding`) +- For Activities with toolbar access, nested bindings remain the same pattern: `binding.appBar.toolbarBinding.centeredToolbar` + +--- + +## Fragment Migration Pattern + +### Before (Kotlin Synthetic) + +```kotlin +import kotlinx.android.synthetic.main.fragment_home.* +import kotlinx.android.synthetic.main.fragment_home.view.* + +class HomeFragment : Fragment(R.layout.fragment_home) { + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + swipeRefreshLayout.setOnRefreshListener { /* ... */ } + recyclerView.adapter = adapter + emptyView.setOnClickListener { /* ... */ } + view?.toolbar?.title = getString(R.string.title) + } +} +``` + +### After (ViewBinding) + +```kotlin +import by.kirich1409.viewbindingdelegate.viewBinding +import org.stepic.droid.databinding.FragmentHomeBinding + +class HomeFragment : Fragment(R.layout.fragment_home) { + + private val homeBinding: FragmentHomeBinding by viewBinding(FragmentHomeBinding::bind) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + homeBinding.swipeRefreshLayout.setOnRefreshListener { /* ... */ } + homeBinding.recyclerView.adapter = adapter + homeBinding.emptyView.setOnClickListener { /* ... */ } + homeBinding.toolbar.title = getString(R.string.title) + } +} +``` + +### Key Points + +- **Keep the Fragment constructor argument** `Fragment(R.layout.xxx)` if present +- The delegate binds via `Fragment.getView()` — no manual binding in `onViewCreated` +- If the Fragment uses `onCreateView` to inflate, keep that and the delegate auto-binds to the returned view +- No `onDestroyView` cleanup needed — the delegate handles lifecycle + +--- + +## DialogFragment Migration Pattern + +### Before (Kotlin Synthetic) + +```kotlin +import kotlinx.android.synthetic.main.dialog_custom.* + +class CustomDialogFragment : DialogFragment() { + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View = inflater.inflate(R.layout.dialog_custom, container, false) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + dialogTitle.text = "Title" + positiveButton.setOnClickListener { dismiss() } + } +} +``` + +### After (ViewBinding) + +```kotlin +import by.kirich1409.viewbindingdelegate.viewBinding +import org.stepic.droid.databinding.DialogCustomBinding + +class CustomDialogFragment : DialogFragment() { + + private val dialogBinding: DialogCustomBinding by viewBinding(DialogCustomBinding::bind) + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View = inflater.inflate(R.layout.dialog_custom, container, false) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + dialogBinding.dialogTitle.text = "Title" + dialogBinding.positiveButton.setOnClickListener { dismiss() } + } +} +``` + +### Key Points + +- **Same `viewBinding()` import and delegate pattern as Activity and Fragment** — no separate dialog API needed +- The library internally detects `DialogFragment` and uses a dialog-lifecycle-aware delegate that binds/unbinds with the dialog view +- Keep `onCreateView` returning the inflated view — the delegate binds to it +- Works identically for `AlertDialog` that use custom views inflated in `onCreateDialog` + +--- + +## BottomSheetDialogFragment Migration Pattern + +**Uses the exact same API as DialogFragment.** No separate guide needed — the patterns are identical. The project already has working examples: + +- `CoursePurchaseBottomSheetDialogFragment` — uses `coursePurchaseBinding: BottomSheetDialogCoursePurchaseBinding by viewBinding(BottomSheetDialogCoursePurchaseBinding::bind)` +- `FilterSearchBottomSheetDialogFragment` — uses `filterRootBinding: BottomSheetDialogFilterSearchBinding by viewBinding(BottomSheetDialogFilterSearchBinding::bind)` + +### Pattern + +```kotlin +// Same import as for every other component type: +import by.kirich1409.viewbindingdelegate.viewBinding +import org.stepic.droid.databinding.BottomSheetXxxBinding + +class XxxBottomSheetDialogFragment : BottomSheetDialogFragment() { + + // Same delegate syntax as Fragment/DialogFragment/Activity: + private val binding: BottomSheetXxxBinding by viewBinding(BottomSheetXxxBinding::bind) + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? = inflater.inflate(R.layout.bottom_sheet_xxx, container, false) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + // Use binding.xxxView instead of synthetic + } +} +``` + +--- + +## Adapter / ViewHolder Migration Pattern + +### Before (Kotlin Synthetic) + +```kotlin +import kotlinx.android.synthetic.main.item_course_news.view.* + +class CourseNewsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + fun bind(data: CourseNews) { + itemView.newsTitle.text = data.title + itemView.newsDate.text = data.date + itemView.newsBadges.isVisible = data.hasBadges + } +} +``` + +### After (ViewBinding) + +```kotlin +import by.kirich1409.viewbindingdelegate.viewBinding +import org.stepic.droid.databinding.ItemCourseNewsBinding + +class CourseNewsViewHolder(root: View) : RecyclerView.ViewHolder(root) { + private val viewBinding: ItemCourseNewsBinding by viewBinding { ItemCourseNewsBinding.bind(root) } + + fun bind(data: CourseNews) { + viewBinding.newsTitle.text = data.title + viewBinding.newsDate.text = data.date + viewBinding.newsBadges.isVisible = data.hasBadges + } +} +``` + +### Key Points + +- Use the lambda form `viewBinding { XxxBinding.bind(root) }` for ViewHolders +- The parameter name changes from `itemView` to `root` (convention in the project) +- Remove the `itemView.` prefix — access views directly from the binding +- For adapter delegates extending `DelegateViewHolder`, follow the same pattern as `CourseNewsAdapterDelegate` in the project + +--- + +## Adapter (non-ViewHolder) Migration Pattern + +Some adapters may use synthetic imports directly on the adapter class. These typically need conversion to use ViewBinding inside `onBindViewHolder`: + +### Before + +```kotlin +import kotlinx.android.synthetic.main.item_social.view.* + +class SocialAuthAdapter : RecyclerView.Adapter() { + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.itemView.socialIcon.setImageResource(icons[position]) + holder.itemView.socialName.setText(names[position]) + } +} +``` + +### After + +```kotlin +import org.stepic.droid.databinding.ItemSocialBinding + +class SocialAuthAdapter : RecyclerView.Adapter() { + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val binding = ItemSocialBinding.bind(holder.itemView) + binding.socialIcon.setImageResource(icons[position]) + binding.socialName.setText(names[position]) + } +} +``` + +Or preferably, store the binding in the ViewHolder using the delegate pattern above. + +--- + +## Utility / Helper Class Migration + +Some utility classes import synthetic for view type helpers, extension functions, or type resolution. These typically do NOT need a binding delegate. + +### Options + +1. **Remove unused synthetic imports** — if the import was only for type access +2. **Pass View as parameter** — instead of accessing via synthetic, accept the View and use `XxxBinding.bind(view)` or `view.findViewById()` +3. **Skip** — if the file is a helper that doesn't directly reference views + +### Example: ToolbarHelper + +```kotlin +// Before +import kotlinx.android.synthetic.main.activity_main.* + +fun Toolbar.setupWithNavController() { /* ... */ } + +// After — synthetic import removed, function unchanged +fun Toolbar.setupWithNavController() { /* ... */ } +``` + +--- + +## Common Pitfalls + +### 1. Multiple synthetic import files + +Some classes import from multiple layouts: + +```kotlin +import kotlinx.android.synthetic.main.fragment_lesson.* +import kotlinx.android.synthetic.main.layout_video_controls.* +``` + +Each layout gets its own binding class. Create separate bindings if needed, or check if the second layout is ``d in the first (in which case one binding covers both). + +### 2. View references in lambdas + +```kotlin +// Before +adapter.setOnItemClickListener { position -> + recyclerView.scrollToPosition(position) + titleView.text = items[position].name +} + +// After +adapter.setOnItemClickListener { position -> + binding.recyclerView.scrollToPosition(position) + binding.titleView.text = items[position].name +} +``` + +### 3. Synthetic references in companion object + +Static synthetic references are not supported. Move to instance methods: + +```kotlin +// Before +companion object { + fun newInstance(title: String): Fragment { + // Cannot use synthetic here, but some code might reference them + } +} + +// After — same, synthetic was never valid here +``` + +### 4. Extension functions on views + +Extension functions defined on view types still work with binding: + +```kotlin +// Before +fun TextView.setStyledText(text: String) { /* ... */ } +textView.setStyledText("hello") + +// After +binding.textView.setStyledText("hello") +``` + +### 5. View IDs with underscores + +XML IDs use snake_case, binding properties use camelCase: + +| XML ID | Binding Property | +|--------|-----------------| +| `root_view` | `rootView` | +| `sign_in_button` | `signInButton` | +| `recycler_view` | `recyclerView` | +| `text_view_title` | `textViewTitle` | + +### 6. Included layouts + +Views from `` are accessible via the parent binding: + +```kotlin +// If activity_main.xml includes layout_toolbar.xml +// binding.toolbarTitle is accessible directly (no separate binding needed) +binding.toolbarTitle.text = "Hello" +``` + +If the included layout has its own binding with `` tag, access via the nested binding property: + +```kotlin +binding.includedLayout.toolbarTitle.text = "Hello" +``` + +**IMPORTANT — `viewBindingIgnore` on included layouts:** Each included layout may also have `tools:viewBindingIgnore="true"`. Check and remove it from every included layout that the migrated code references. See step 2.3b in the main skill for details. + +**IMPORTANT — Passing included layouts to delegates:** When a delegate/helper class takes a `View` parameter representing an included layout's root, use `.root` on the included binding: + +```kotlin +// Synthetic: root.achievementTile was a View +val delegate = SomeDelegate(root.achievementTile, resolver) + +// ViewBinding: binding.achievementTile is the included layout's binding class +// Use .root to get the View (equivalent to the synthetic behavior) +val delegate = SomeDelegate(binding.achievementTile.root, resolver) +``` diff --git a/.claude/skills/migrate-to-viewbinding/references/testing-guide.md b/.claude/skills/migrate-to-viewbinding/references/testing-guide.md new file mode 100644 index 0000000000..9112979baf --- /dev/null +++ b/.claude/skills/migrate-to-viewbinding/references/testing-guide.md @@ -0,0 +1,156 @@ +# Testing Guide for ViewBinding Migration + +## Compilation Verification (Per File) + +After migrating each file, verify that the module compiles: + +```bash +./gradlew :app:compileDebugKotlin 2>&1 | tail -50 +``` + +This catches: +- Missing binding class imports +- Incorrect view property names (wrong camelCase conversion) +- Type mismatches between synthetic and binding +- Unresolved references from removed imports + +### Faster variant-specific check + +```bash +# Debug variant (default) +./gradlew :app:compileDebugKotlin + +# Release variant +./gradlew :app:compileReleaseKotlin +``` + +--- + +## Per-File Verification Checklist + +After completing each migration, verify these items: + +- [ ] No `kotlinx.android.synthetic` imports remain in the file +- [ ] `tools:viewBindingIgnore="true"` removed from the layout XML root element +- [ ] Binding delegate declared with correct type: `private val xxxBinding: XxxBinding by viewBinding(XxxBinding::bind)` +- [ ] Correct import for `by.kirich1409.viewbindingdelegate.viewBinding` +- [ ] Correct import for the generated binding class (`org.stepic.droid.databinding.XxxBinding`) +- [ ] All synthetic view references replaced with `binding.viewId` +- [ ] Snake_case IDs converted to camelCase properties +- [ ] Layout inflation preserved (`setContentView` for Activities, `onCreateView` return for Fragments) +- [ ] No references to bare view IDs (e.g., `textView.text` without `binding.` prefix) + +--- + +## Common Errors and Fixes + +### "Unresolved reference: XxxBinding" + +**Cause:** The binding class does not exist or is not in the expected package. + +**Fix:** +1. Verify the layout XML file exists: check for `app/src/main/res/layout/xxx.xml` +2. **Check for `tools:viewBindingIgnore="true"` on the layout's root element** — this is the most common cause in this project. Remove it if present (see step 2.3a in the main skill). +3. Verify viewBinding is enabled: check `buildFeatures { viewBinding true }` in `app/build.gradle` +4. Check the binding class package: run `./gradlew :app:compileDebugKotlin` and check generated sources in `app/build/generated/data_binding_base_class_source_out/` +5. For layouts in different modules, the binding class package may differ + +### "Cannot access 'viewId' on 'XxxBinding'" + +**Cause:** The view ID does not exist in the layout XML file. + +**Fix:** +1. Open the layout XML and verify the view ID exists +2. The synthetic may have referenced a view from an ``d layout — check if the include has a `` tag +3. The view may be defined in a different layout file — check other synthetic imports + +### "Property delegate must have a 'getValue' method" + +**Cause:** The `viewBinding` delegate import is missing. + +**Fix:** Add `import by.kirich1409.viewbindingdelegate.viewBinding` + +### "Type mismatch" errors after migration + +**Cause:** Synthetic properties infer types from `findViewById`, while binding classes use the exact XML `class` attribute. + +**Fix:** +1. Check the XML `class` attribute matches the expected Kotlin type +2. Cast explicitly if types differ: `binding.view as SpecificType` +3. Update the XML to use the correct `class` attribute + +### "Overload resolution ambiguity" + +**Cause:** Multiple methods match after removing the synthetic import that disambiguated the call. + +**Fix:** Add explicit type parameters or qualify the method call. + +### "Val cannot be reassigned" + +**Cause:** Synthetic references could be directly reassigned in rare cases. Binding properties are read-only. + +**Fix:** Use the binding's setter methods instead of reassignment. + +--- + +## Running Tests (Batch Verification) + +After migrating a batch of files, run the project's test suite: + +```bash +# Unit tests +./gradlew :app:testDebugUnitTest + +# Android instrumented tests (requires emulator or device) +./gradlew :app:connectedDebugAndroidTest +``` + +If individual test classes relate to migrated screens, prioritize running those: + +```bash +./gradlew :app:testDebugUnitTest --tests "org.stepik.android.view.auth.*" +``` + +--- + +## Verification: No Remaining Synthetic Imports + +After completing all migrations, verify the entire project is clean: + +```bash +# Should return 0 results +grep -rn "kotlinx.android.synthetic" --include="*.kt" app/src/ + +# Count remaining files (should be 0) +grep -rn "kotlinx.android.synthetic" --include="*.kt" -l app/src/ | wc -l +``` + +Also verify the Kotlin Android Extensions plugin can be removed (check `build.gradle`): + +```bash +# Check for kotlin-android-extensions plugin +grep -rn "android.extensions\|kotlin-android-extensions" --include="*.gradle" . +``` + +If the plugin is still declared and no synthetic imports remain, it can be safely removed. If the project uses `@Parcelize`, switch to the `kotlin-parcelize` plugin first. + +--- + +## Manual Verification (Critical Screens) + +For high-traffic or critical screens, manual testing is recommended: + +1. Build and install the debug variant: `./gradlew :app:assembleDebug` +2. Navigate to each migrated screen +3. Verify all UI elements render correctly +4. Test all interactions (button clicks, text input, scrolling, selections) +5. Verify no `ClassCastException` or `NullPointerException` in logcat +6. Test configuration changes (rotation) if the screen preserves state +7. Test back navigation and fragment transactions + +### Screens to prioritize for manual testing + +- Login/registration screens (critical user flow) +- Payment screens (financial impact) +- Main feed and navigation (core UX) +- Any screens with complex view hierarchies or animations From c1499a12063c1fac91e67c5fca0c0f61b189d272 Mon Sep 17 00:00:00 2001 From: Aleksandr Zhukov Date: Tue, 2 Jun 2026 19:36:16 +0200 Subject: [PATCH 02/65] chore: remove viewBindingIgnore from view_achievement_tile layout This included layout is referenced by AchievementAdapterDelegate via . The viewBindingIgnore attribute was blocking ViewBinding class generation for the included layout. Co-Authored-By: Claude Opus 4.8 --- app/src/main/res/layout/view_achievement_tile.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/res/layout/view_achievement_tile.xml b/app/src/main/res/layout/view_achievement_tile.xml index fafdc4beab..c3752a7dd2 100644 --- a/app/src/main/res/layout/view_achievement_tile.xml +++ b/app/src/main/res/layout/view_achievement_tile.xml @@ -6,8 +6,7 @@ android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content" - android:gravity="center_horizontal" - tools:viewBindingIgnore="true"> + android:gravity="center_horizontal"> Date: Tue, 2 Jun 2026 19:36:23 +0200 Subject: [PATCH 03/65] refactor: migrate 5 files from synthetic to viewBinding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - RateAppDialog.kt (DialogFragment) — 10 view refs - FastContinueFragment.kt (Fragment) — 13 view refs - CourseBenefitsAdapterDelegate.kt (Adapter/ViewHolder) — 9 view refs, removed LayoutContainer - DownloadedCoursesAdapterDelegate.kt (Adapter/ViewHolder) — 4 view refs - AchievementAdapterDelegate.kt (Adapter/ViewHolder) — 3 view refs, uses .root on include Also removed tools:viewBindingIgnore from corresponding layout XMLs. Co-Authored-By: Claude Opus 4.8 --- .../delegate/AchievementAdapterDelegate.kt | 13 +++--- .../app_rating/ui/dialog/RateAppDialog.kt | 37 +++++++++-------- .../delegate/CourseBenefitsAdapterDelegate.kt | 23 ++++++----- .../DownloadedCoursesAdapterDelegate.kt | 18 ++++----- .../ui/fragment/FastContinueFragment.kt | 40 ++++++++++--------- app/src/main/res/layout/dialog_rate_app.xml | 3 +- .../res/layout/downloaded_course_item.xml | 2 +- .../res/layout/fragment_fast_continue.xml | 2 +- .../main/res/layout/item_course_benefit.xml | 2 +- .../main/res/layout/view_achievement_item.xml | 2 +- 10 files changed, 72 insertions(+), 70 deletions(-) diff --git a/app/src/main/java/org/stepik/android/view/achievement/ui/adapter/delegate/AchievementAdapterDelegate.kt b/app/src/main/java/org/stepik/android/view/achievement/ui/adapter/delegate/AchievementAdapterDelegate.kt index 90642f7d07..602e685738 100644 --- a/app/src/main/java/org/stepik/android/view/achievement/ui/adapter/delegate/AchievementAdapterDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/achievement/ui/adapter/delegate/AchievementAdapterDelegate.kt @@ -2,9 +2,9 @@ package org.stepik.android.view.achievement.ui.adapter.delegate import android.view.View import android.view.ViewGroup -import android.widget.TextView -import kotlinx.android.synthetic.main.view_achievement_item.view.* +import by.kirich1409.viewbindingdelegate.viewBinding import org.stepic.droid.R +import org.stepic.droid.databinding.ViewAchievementItemBinding import org.stepik.android.view.achievement.ui.resolver.AchievementResourceResolver import org.stepik.android.domain.achievement.model.AchievementItem import org.stepik.android.view.achievement.ui.delegate.AchievementTileDelegate @@ -22,10 +22,9 @@ class AchievementAdapterDelegate( ViewHolder(createView(parent, R.layout.view_achievement_item)) private inner class ViewHolder(root: View) : DelegateViewHolder(root) { - private val achievementTitle: TextView = root.achievementTitle - private val achievementDescription: TextView = root.achievementDescription + private val viewBinding: ViewAchievementItemBinding by viewBinding { ViewAchievementItemBinding.bind(root) } - private val achievementTileDelegate = AchievementTileDelegate(root.achievementTile, achievementResourceResolver) + private val achievementTileDelegate = AchievementTileDelegate(viewBinding.achievementTile.root, achievementResourceResolver) init { root.setOnClickListener { itemData?.let(onItemClicked) } @@ -34,8 +33,8 @@ class AchievementAdapterDelegate( override fun onBind(data: AchievementItem) { achievementTileDelegate.setAchievement(data) - achievementTitle.text = achievementResourceResolver.resolveTitleForKind(data.kind) - achievementDescription.text = achievementResourceResolver.resolveDescription(data) + viewBinding.achievementTitle.text = achievementResourceResolver.resolveTitleForKind(data.kind) + viewBinding.achievementDescription.text = achievementResourceResolver.resolveDescription(data) } } } \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/app_rating/ui/dialog/RateAppDialog.kt b/app/src/main/java/org/stepik/android/view/app_rating/ui/dialog/RateAppDialog.kt index ba8641f839..f7a2e7255f 100644 --- a/app/src/main/java/org/stepik/android/view/app_rating/ui/dialog/RateAppDialog.kt +++ b/app/src/main/java/org/stepik/android/view/app_rating/ui/dialog/RateAppDialog.kt @@ -8,8 +8,9 @@ import android.widget.TextView import androidx.annotation.AttrRes import androidx.annotation.StringRes import androidx.fragment.app.DialogFragment -import kotlinx.android.synthetic.main.dialog_rate_app.* +import by.kirich1409.viewbindingdelegate.viewBinding import org.stepic.droid.R +import org.stepic.droid.databinding.DialogRateAppBinding import org.stepic.droid.analytic.Analytic import org.stepic.droid.base.App import org.stepic.droid.util.RatingUtil @@ -37,6 +38,8 @@ class RateAppDialog : DialogFragment() { fun onClickSupport(starNumber: Int) } } + private val rateAppBinding: DialogRateAppBinding by viewBinding(DialogRateAppBinding::bind) + @Inject lateinit var analytic: Analytic @@ -54,14 +57,14 @@ class RateAppDialog : DialogFragment() { val callback = targetFragment as? Callback ?: activity as Callback - rateDialogLater.setOnClickListener { + rateAppBinding.rateDialogLater.setOnClickListener { dialog?.dismiss() - callback.onClickLater(rateDialogRatingBar.rating.toInt()) + callback.onClickLater(rateAppBinding.rateDialogRatingBar.rating.toInt()) } - rateDialogPositive.setOnClickListener { + rateAppBinding.rateDialogPositive.setOnClickListener { dialog?.dismiss() - val rating = rateDialogRatingBar.rating.toInt() + val rating = rateAppBinding.rateDialogRatingBar.rating.toInt() if (RatingUtil.isExcellent(rating)) { callback.onClickGooglePlay(rating) } else { @@ -69,7 +72,7 @@ class RateAppDialog : DialogFragment() { } } - rateDialogRatingBar.setOnRatingBarChangeListener { _, rating, fromUser -> + rateAppBinding.rateDialogRatingBar.setOnRatingBarChangeListener { _, rating, fromUser -> if (!fromUser) { return@setOnRatingBarChangeListener } @@ -87,28 +90,28 @@ class RateAppDialog : DialogFragment() { private fun applyRating(rating: Int) { if (rating == 0) { - rateDialogTitle.setText(R.string.rate_dialog_title) - rateDialogButtonsContainer.visibility = View.GONE - rateDialogHint.visibility = View.GONE + rateAppBinding.rateDialogTitle.setText(R.string.rate_dialog_title) + rateAppBinding.rateDialogButtonsContainer.visibility = View.GONE + rateAppBinding.rateDialogHint.visibility = View.GONE } else { - rateDialogHint.visibility = View.VISIBLE - rateDialogTitle.setText(R.string.rate_dialog_thanks) + rateAppBinding.rateDialogHint.visibility = View.VISIBLE + rateAppBinding.rateDialogTitle.setText(R.string.rate_dialog_thanks) if (rating in 1..4) { - rateDialogHint.setText(R.string.rate_dialog_hint_negative) - rateDialogPositive.setTextAndColor(R.string.rate_dialog_support, R.attr.colorError) + rateAppBinding.rateDialogHint.setText(R.string.rate_dialog_hint_negative) + rateAppBinding.rateDialogPositive.setTextAndColor(R.string.rate_dialog_support, R.attr.colorError) } else if (RatingUtil.isExcellent(rating)) { - rateDialogHint.setText(R.string.rate_dialog_hint_positive) - rateDialogPositive.setTextAndColor(R.string.rate_dialog_google_play, R.attr.colorSecondary) + rateAppBinding.rateDialogHint.setText(R.string.rate_dialog_hint_positive) + rateAppBinding.rateDialogPositive.setTextAndColor(R.string.rate_dialog_google_play, R.attr.colorSecondary) } - rateDialogButtonsContainer.visibility = View.VISIBLE + rateAppBinding.rateDialogButtonsContainer.visibility = View.VISIBLE } } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - outState.putInt(ratingKey, rateDialogRatingBar.rating.toInt()) + outState.putInt(ratingKey, rateAppBinding.rateDialogRatingBar.rating.toInt()) } private fun TextView.setTextAndColor(@StringRes stringRes: Int, @AttrRes textColorRes: Int) { diff --git a/app/src/main/java/org/stepik/android/view/course_revenue/ui/adapter/delegate/CourseBenefitsAdapterDelegate.kt b/app/src/main/java/org/stepik/android/view/course_revenue/ui/adapter/delegate/CourseBenefitsAdapterDelegate.kt index aa48756ba7..3db8eab2a2 100644 --- a/app/src/main/java/org/stepik/android/view/course_revenue/ui/adapter/delegate/CourseBenefitsAdapterDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/course_revenue/ui/adapter/delegate/CourseBenefitsAdapterDelegate.kt @@ -8,9 +8,9 @@ import androidx.appcompat.content.res.AppCompatResources import androidx.core.content.ContextCompat import androidx.core.graphics.drawable.DrawableCompat import androidx.core.view.isVisible -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_course_benefit.* +import by.kirich1409.viewbindingdelegate.viewBinding import org.stepic.droid.R +import org.stepic.droid.databinding.ItemCourseBenefitBinding import org.stepic.droid.util.DateTimeHelper import org.stepik.android.domain.course_revenue.model.CourseBenefit import org.stepik.android.domain.course_revenue.model.CourseBenefitListItem @@ -33,7 +33,8 @@ class CourseBenefitsAdapterDelegate( private inner class ViewHolder( override val containerView: View - ) : DelegateViewHolder(containerView), LayoutContainer { + ) : DelegateViewHolder(containerView) { + private val viewBinding: ItemCourseBenefitBinding by viewBinding { ItemCourseBenefitBinding.bind(root) } init { itemView.setOnClickListener { (itemData as? CourseBenefitListItem.Data)?.let { onItemClick(it) } } @@ -46,8 +47,8 @@ class CourseBenefitsAdapterDelegate( val decimalFormat = DecimalFormat().apply { setCurrency(currency) } decimalFormat.minimumFractionDigits = 2 - purchaseRefundIcon.setImageDrawable(getIconDrawable(data.courseBenefit)) - purchaseRefundName.text = + viewBinding.purchaseRefundIcon.setImageDrawable(getIconDrawable(data.courseBenefit)) + viewBinding.purchaseRefundName.text = if (data.courseBenefit.buyer == null && !data.courseBenefit.isInvoicePayment) { buildString { append(context.getString(R.string.transaction_manual_channel)) @@ -59,7 +60,7 @@ class CourseBenefitsAdapterDelegate( data.user?.fullName ?: data.courseBenefit.buyer.toString() } - purchaseRefundDate.text = DateTimeHelper.getPrintableDate( + viewBinding.purchaseRefundDate.text = DateTimeHelper.getPrintableDate( data.courseBenefit.time, DateTimeHelper.DISPLAY_DATETIME_PATTERN, TimeZone.getDefault() @@ -82,11 +83,11 @@ class CourseBenefitsAdapterDelegate( } else { ContextCompat.getColor(context, R.color.color_overlay_red) } - purchaseRefundIncomeSum.setTextColor(textColor) - purchaseRefundTransactionSum.text = transactionSum - purchaseRefundIncomeSum.text = amount - purchaseRefundPromocode.text = data.courseBenefit.promoCode - purchaseRefundPromocode.isVisible = data.courseBenefit.promoCode != null + viewBinding.purchaseRefundIncomeSum.setTextColor(textColor) + viewBinding.purchaseRefundTransactionSum.text = transactionSum + viewBinding.purchaseRefundIncomeSum.text = amount + viewBinding.purchaseRefundPromocode.text = data.courseBenefit.promoCode + viewBinding.purchaseRefundPromocode.isVisible = data.courseBenefit.promoCode != null } private fun getIconDrawable(data: CourseBenefit): Drawable? = diff --git a/app/src/main/java/org/stepik/android/view/download/ui/adapter/DownloadedCoursesAdapterDelegate.kt b/app/src/main/java/org/stepik/android/view/download/ui/adapter/DownloadedCoursesAdapterDelegate.kt index 539c50a643..7cb064d169 100644 --- a/app/src/main/java/org/stepik/android/view/download/ui/adapter/DownloadedCoursesAdapterDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/download/ui/adapter/DownloadedCoursesAdapterDelegate.kt @@ -2,9 +2,10 @@ package org.stepik.android.view.download.ui.adapter import android.view.View import android.view.ViewGroup +import by.kirich1409.viewbindingdelegate.viewBinding import com.bumptech.glide.Glide -import kotlinx.android.synthetic.main.downloaded_course_item.view.* import org.stepic.droid.R +import org.stepic.droid.databinding.DownloadedCourseItemBinding import org.stepic.droid.persistence.model.DownloadItem import org.stepic.droid.persistence.model.DownloadProgress import ru.nobird.android.ui.adapterdelegates.AdapterDelegate @@ -24,30 +25,27 @@ class DownloadedCoursesAdapterDelegate( ViewHolder(createView(parent, R.layout.downloaded_course_item)) private inner class ViewHolder(root: View) : DelegateViewHolder(root) { - - private val downloadedCourseTitle = root.downloadedCourseName - private val downloadedCourseImage = root.downloadedCourseImage - private val downloadedCourseStatus = root.downloadedCourseStatus + private val viewBinding: DownloadedCourseItemBinding by viewBinding { DownloadedCourseItemBinding.bind(root) } init { root.setOnClickListener { itemData?.let(onItemClick) } - downloadedCourseStatus.setOnClickListener { - if (downloadedCourseStatus.status is DownloadProgress.Status.Cached) { + viewBinding.downloadedCourseStatus.setOnClickListener { + if (viewBinding.downloadedCourseStatus.status is DownloadProgress.Status.Cached) { itemData?.let(onItemRemoveClick) } } } override fun onBind(data: DownloadItem) { - downloadedCourseTitle.text = data.course.title - downloadedCourseStatus.status = data.status + viewBinding.downloadedCourseTitle.text = data.course.title + viewBinding.downloadedCourseStatus.status = data.status Glide.with(context) .asBitmap() .load(data.course.cover) .placeholder(R.drawable.general_placeholder) .fitCenter() - .into(downloadedCourseImage) + .into(viewBinding.downloadedCourseImage) } } } \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/fast_continue/ui/fragment/FastContinueFragment.kt b/app/src/main/java/org/stepik/android/view/fast_continue/ui/fragment/FastContinueFragment.kt index df8f20ac3c..f861f67dbe 100644 --- a/app/src/main/java/org/stepik/android/view/fast_continue/ui/fragment/FastContinueFragment.kt +++ b/app/src/main/java/org/stepik/android/view/fast_continue/ui/fragment/FastContinueFragment.kt @@ -8,9 +8,10 @@ import androidx.fragment.app.DialogFragment import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.lifecycle.ViewModelProvider +import by.kirich1409.viewbindingdelegate.viewBinding import com.bumptech.glide.Glide -import kotlinx.android.synthetic.main.fragment_fast_continue.* import org.stepic.droid.R +import org.stepic.droid.databinding.FragmentFastContinueBinding import org.stepic.droid.analytic.Analytic import org.stepic.droid.base.App import org.stepic.droid.core.ScreenManager @@ -43,6 +44,7 @@ class FastContinueFragment : Fragment(R.layout.fragment_fast_continue), FastCont @Inject internal lateinit var screenManager: ScreenManager + private val fastContinueBinding: FragmentFastContinueBinding by viewBinding(FragmentFastContinueBinding::bind) private val fastContinuePresenter: FastContinuePresenter by viewModels { viewModelFactory } private lateinit var viewStateDelegate: ViewStateDelegate @@ -66,13 +68,13 @@ class FastContinueFragment : Fragment(R.layout.fragment_fast_continue), FastCont super.onViewCreated(view, savedInstanceState) viewStateDelegate = ViewStateDelegate() viewStateDelegate.addState() - viewStateDelegate.addState(fastContinueProgress) - viewStateDelegate.addState(fastContinuePlaceholder) - viewStateDelegate.addState(fastContinuePlaceholder) - viewStateDelegate.addState(fastContinueMask) + viewStateDelegate.addState(fastContinueBinding.fastContinueProgress) + viewStateDelegate.addState(fastContinueBinding.fastContinuePlaceholder) + viewStateDelegate.addState(fastContinueBinding.fastContinuePlaceholder) + viewStateDelegate.addState(fastContinueBinding.fastContinueMask) - fastContinueOverlay.isEnabled = true - fastContinueAction.isEnabled = true + fastContinueBinding.fastContinueOverlay.isEnabled = true + fastContinueBinding.fastContinueAction.isEnabled = true } override fun onStart() { @@ -105,8 +107,8 @@ class FastContinueFragment : Fragment(R.layout.fragment_fast_continue), FastCont is FastContinueView.State.Content -> { analytic.reportEvent(Analytic.FastContinue.CONTINUE_SHOWN) setCourse(state.courseListItem) - fastContinueOverlay.setOnClickListener { handleContinueCourseClick(state.courseListItem.course) } - fastContinueAction.setOnClickListener { handleContinueCourseClick(state.courseListItem.course) } + fastContinueBinding.fastContinueOverlay.setOnClickListener { handleContinueCourseClick(state.courseListItem.course) } + fastContinueBinding.fastContinueAction.setOnClickListener { handleContinueCourseClick(state.courseListItem.course) } } else -> Unit } @@ -119,9 +121,9 @@ class FastContinueFragment : Fragment(R.layout.fragment_fast_continue), FastCont .load(courseListItem.course.cover) .placeholder(R.drawable.general_placeholder) .fitCenter() - .into(fastContinueCourseCover) + .into(fastContinueBinding.fastContinueCourseCover) - fastContinueCourseName.text = courseListItem.course.title + fastContinueBinding.fastContinueCourseName.text = courseListItem.course.title val progress = courseListItem.courseStats.progress val needShow = if (progress != null && progress.cost > 0f) { @@ -130,19 +132,19 @@ class FastContinueFragment : Fragment(R.layout.fragment_fast_continue), FastCont ?.toFloatOrNull() ?: 0f - fastContinueCourseProgressText.text = getString(R.string.course_current_progress, score.toFixed(resources.getInteger(R.integer.score_decimal_count)), progress.cost) - fastContinueCourseProgress.progress = (score * 100 / progress.cost).toInt() + fastContinueBinding.fastContinueCourseProgressText.text = getString(R.string.course_current_progress, score.toFixed(resources.getInteger(R.integer.score_decimal_count)), progress.cost) + fastContinueBinding.fastContinueCourseProgress.progress = (score * 100 / progress.cost).toInt() true } else { - fastContinueCourseProgress.progress = 0 + fastContinueBinding.fastContinueCourseProgress.progress = 0 false } - fastContinueCourseProgressText.isVisible = needShow + fastContinueBinding.fastContinueCourseProgressText.isVisible = needShow } private fun showPlaceholder(@StringRes stringRes: Int, listener: (view: View) -> Unit) { - fastContinuePlaceholder.setPlaceholderText(stringRes) - fastContinuePlaceholder.setOnClickListener(listener) + fastContinueBinding.fastContinuePlaceholder.setPlaceholderText(stringRes) + fastContinueBinding.fastContinuePlaceholder.setOnClickListener(listener) } private fun handleContinueCourseClick(course: Course) { @@ -163,8 +165,8 @@ class FastContinueFragment : Fragment(R.layout.fragment_fast_continue), FastCont } override fun setBlockingLoading(isLoading: Boolean) { - fastContinueOverlay.isEnabled = !isLoading - fastContinueAction.isEnabled = !isLoading + fastContinueBinding.fastContinueOverlay.isEnabled = !isLoading + fastContinueBinding.fastContinueAction.isEnabled = !isLoading if (isLoading) { ProgressHelper.activate(progressDialogFragment, fragmentManager, LoadingProgressDialogFragment.TAG) } else { diff --git a/app/src/main/res/layout/dialog_rate_app.xml b/app/src/main/res/layout/dialog_rate_app.xml index aff18482dc..654027707c 100644 --- a/app/src/main/res/layout/dialog_rate_app.xml +++ b/app/src/main/res/layout/dialog_rate_app.xml @@ -3,8 +3,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="wrap_content" - tools:viewBindingIgnore="true"> + android:layout_height="wrap_content"> +> +> +> +> Date: Tue, 2 Jun 2026 19:50:14 +0200 Subject: [PATCH 04/65] refactor: migrate 6 files from synthetic to viewBinding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - InAppPurchaseAdapterDelegate.kt (debug + stageDebuggable) - SplitTestDataAdapterDelegate.kt (debug + stageDebuggable) - AdaptiveStatsActivity.kt - AdaptiveRatingAdapter.kt - AdaptiveWeeksAdapter.kt - QuizCardViewHolder.kt Also fix pre-existing issues in previously migrated files: - AchievementAdapterDelegate: use itemView instead of root - CourseBenefitsAdapterDelegate: remove invalid override, use itemView - DownloadedCoursesAdapterDelegate: fix downloadedCourseTitle → downloadedCourseName, use itemView Remove viewBindingIgnore from 5 layout XMLs. Co-Authored-By: Claude Opus 4.8 --- .../delegate/InAppPurchaseAdapterDelegate.kt | 26 ++++++------ .../delegate/SplitTestDataAdapterDelegate.kt | 14 ++++--- .../ui/activities/AdaptiveStatsActivity.kt | 18 +++++---- .../ui/adapters/AdaptiveRatingAdapter.kt | 13 +++--- .../ui/adapters/AdaptiveWeeksAdapter.kt | 22 +++++----- .../ui/adapters/QuizCardViewHolder.kt | 40 ++++++++++--------- .../delegate/AchievementAdapterDelegate.kt | 2 +- .../delegate/CourseBenefitsAdapterDelegate.kt | 4 +- .../DownloadedCoursesAdapterDelegate.kt | 4 +- .../res/layout/activity_adaptive_stats.xml | 4 +- .../main/res/layout/adaptive_header_stats.xml | 4 +- .../main/res/layout/adaptive_item_week.xml | 2 +- .../res/layout/adaptive_quiz_card_view.xml | 2 +- .../main/res/layout/adaptive_rating_item.xml | 2 +- .../delegate/InAppPurchaseAdapterDelegate.kt | 26 ++++++------ .../delegate/SplitTestDataAdapterDelegate.kt | 14 ++++--- 16 files changed, 104 insertions(+), 93 deletions(-) diff --git a/app/src/debug/java/org/stepik/android/view/debug/ui/adapter/delegate/InAppPurchaseAdapterDelegate.kt b/app/src/debug/java/org/stepik/android/view/debug/ui/adapter/delegate/InAppPurchaseAdapterDelegate.kt index 7dc934bf2a..a40ae08576 100644 --- a/app/src/debug/java/org/stepik/android/view/debug/ui/adapter/delegate/InAppPurchaseAdapterDelegate.kt +++ b/app/src/debug/java/org/stepik/android/view/debug/ui/adapter/delegate/InAppPurchaseAdapterDelegate.kt @@ -1,14 +1,12 @@ package org.stepik.android.view.debug.ui.adapter.delegate -import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible +import by.kirich1409.viewbindingdelegate.viewBinding import com.android.billingclient.api.Purchase -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.debug.item_in_app_purchase.* import org.stepic.droid.R +import org.stepic.droid.databinding.ItemInAppPurchaseBinding import org.stepic.droid.util.DateTimeHelper -import org.stepic.droid.util.toObject import org.stepik.android.domain.course.model.CoursePurchasePayload import ru.nobird.android.ui.adapterdelegates.AdapterDelegate import ru.nobird.android.ui.adapterdelegates.DelegateViewHolder @@ -24,17 +22,21 @@ class InAppPurchaseAdapterDelegate( override fun onCreateViewHolder(parent: ViewGroup): DelegateViewHolder = ViewHolder(createView(parent, R.layout.item_in_app_purchase)) - private inner class ViewHolder(override val containerView: View) : DelegateViewHolder(containerView), LayoutContainer { + private inner class ViewHolder( + containerView: android.view.View + ) : DelegateViewHolder(containerView) { + private val viewBinding: ItemInAppPurchaseBinding by viewBinding { ItemInAppPurchaseBinding.bind(itemView) } + init { - inAppPurchaseConsumeAction.setOnClickListener { itemData?.let(onItemClick) } + viewBinding.inAppPurchaseConsumeAction.setOnClickListener { itemData?.let(onItemClick) } } override fun onBind(data: Purchase) { - inAppPurchaseSku.text = data.skus.first() - inAppPurchaseTime.text = context.getString(R.string.debug_purchase_date, DateTimeHelper.getPrintableDate(Date(data.purchaseTime), DateTimeHelper.DISPLAY_DATETIME_PATTERN, TimeZone.getDefault())) - inAppPurchaseStatus.text = context.getString(R.string.debug_purchase_status, data.purchaseState.toString()) + viewBinding.inAppPurchaseSku.text = data.skus.first() + viewBinding.inAppPurchaseTime.text = context.getString(R.string.debug_purchase_date, DateTimeHelper.getPrintableDate(Date(data.purchaseTime), DateTimeHelper.DISPLAY_DATETIME_PATTERN, TimeZone.getDefault())) + viewBinding.inAppPurchaseStatus.text = context.getString(R.string.debug_purchase_status, data.purchaseState.toString()) - inAppPurchaseCourse.isVisible = data.developerPayload.isNotEmpty() - inAppPurchaseUser.isVisible = data.developerPayload.isNotEmpty() + viewBinding.inAppPurchaseCourse.isVisible = data.developerPayload.isNotEmpty() + viewBinding.inAppPurchaseUser.isVisible = data.developerPayload.isNotEmpty() } } -} \ No newline at end of file +} diff --git a/app/src/debug/java/org/stepik/android/view/debug/ui/adapter/delegate/SplitTestDataAdapterDelegate.kt b/app/src/debug/java/org/stepik/android/view/debug/ui/adapter/delegate/SplitTestDataAdapterDelegate.kt index 6f6bd20719..454b573f13 100644 --- a/app/src/debug/java/org/stepik/android/view/debug/ui/adapter/delegate/SplitTestDataAdapterDelegate.kt +++ b/app/src/debug/java/org/stepik/android/view/debug/ui/adapter/delegate/SplitTestDataAdapterDelegate.kt @@ -2,9 +2,9 @@ package org.stepik.android.view.debug.ui.adapter.delegate import android.view.View import android.view.ViewGroup -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_split_test_data.* +import by.kirich1409.viewbindingdelegate.viewBinding import org.stepic.droid.R +import org.stepic.droid.databinding.ItemSplitTestDataBinding import org.stepik.android.domain.debug.model.SplitTestData import ru.nobird.android.ui.adapterdelegates.AdapterDelegate import ru.nobird.android.ui.adapterdelegates.DelegateViewHolder @@ -18,13 +18,15 @@ class SplitTestDataAdapterDelegate( override fun onCreateViewHolder(parent: ViewGroup): DelegateViewHolder = ViewHolder(createView(parent, R.layout.item_split_test_data)) - private inner class ViewHolder(override val containerView: View) : DelegateViewHolder(containerView), LayoutContainer { + private inner class ViewHolder(containerView: View) : DelegateViewHolder(containerView) { + private val viewBinding: ItemSplitTestDataBinding by viewBinding { ItemSplitTestDataBinding.bind(itemView) } + init { containerView.setOnClickListener { itemData?.let { onItemClick(it.splitTestName, it.splitTestValue, it.splitTestGroups) } } } override fun onBind(data: SplitTestData) { - splitTestGroupTitle.text = data.splitTestName - splitTestGroupValue.text = data.splitTestValue + viewBinding.splitTestGroupTitle.text = data.splitTestName + viewBinding.splitTestGroupValue.text = data.splitTestValue } } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/stepic/droid/adaptive/ui/activities/AdaptiveStatsActivity.kt b/app/src/main/java/org/stepic/droid/adaptive/ui/activities/AdaptiveStatsActivity.kt index b288349424..716abc4c51 100644 --- a/app/src/main/java/org/stepic/droid/adaptive/ui/activities/AdaptiveStatsActivity.kt +++ b/app/src/main/java/org/stepic/droid/adaptive/ui/activities/AdaptiveStatsActivity.kt @@ -4,13 +4,14 @@ import android.os.Bundle import android.view.MenuItem import androidx.appcompat.app.AppCompatDelegate import androidx.viewpager2.widget.ViewPager2 +import by.kirich1409.viewbindingdelegate.viewBinding import com.google.android.material.tabs.TabLayoutMediator -import kotlinx.android.synthetic.main.activity_adaptive_stats.* import org.stepic.droid.R import org.stepic.droid.adaptive.model.AdaptiveStatsTabs import org.stepic.droid.adaptive.ui.adapters.AdaptiveStatsViewPagerAdapter import org.stepic.droid.analytic.AmplitudeAnalytic import org.stepic.droid.base.FragmentActivityBase +import org.stepic.droid.databinding.ActivityAdaptiveStatsBinding import org.stepic.droid.ui.util.initCenteredToolbar import org.stepic.droid.util.AppConstants @@ -21,6 +22,7 @@ class AdaptiveStatsActivity : FragmentActivityBase() { } } + private val adaptiveStatsBinding: ActivityAdaptiveStatsBinding by viewBinding(ActivityAdaptiveStatsBinding::bind) private var courseId: Long = 0 private var hasSavedInstanceState: Boolean = false private lateinit var adapter: AdaptiveStatsViewPagerAdapter @@ -46,24 +48,24 @@ class AdaptiveStatsActivity : FragmentActivityBase() { adapter = AdaptiveStatsViewPagerAdapter(this, courseId) - pager.adapter = adapter - pager.offscreenPageLimit = adapter.itemCount + adaptiveStatsBinding.pager.adapter = adapter + adaptiveStatsBinding.pager.offscreenPageLimit = adapter.itemCount - TabLayoutMediator(tabLayout, pager) { tab, position -> + TabLayoutMediator(adaptiveStatsBinding.tabLayout, adaptiveStatsBinding.pager) { tab, position -> tab.setText(AdaptiveStatsTabs.values()[position].fragmentTitleRes) }.attach() } override fun onResume() { super.onResume() - pager.registerOnPageChangeCallback(onPageChangeListener) - if (!hasSavedInstanceState && pager.currentItem == 0) { + adaptiveStatsBinding.pager.registerOnPageChangeCallback(onPageChangeListener) + if (!hasSavedInstanceState && adaptiveStatsBinding.pager.currentItem == 0) { onPageChangeListener.onPageSelected(0) } } override fun onPause() { - pager.unregisterOnPageChangeCallback(onPageChangeListener) + adaptiveStatsBinding.pager.unregisterOnPageChangeCallback(onPageChangeListener) super.onPause() } @@ -74,4 +76,4 @@ class AdaptiveStatsActivity : FragmentActivityBase() { } return super.onOptionsItemSelected(item) } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/stepic/droid/adaptive/ui/adapters/AdaptiveRatingAdapter.kt b/app/src/main/java/org/stepic/droid/adaptive/ui/adapters/AdaptiveRatingAdapter.kt index c7efd9ee79..68c106969e 100644 --- a/app/src/main/java/org/stepic/droid/adaptive/ui/adapters/AdaptiveRatingAdapter.kt +++ b/app/src/main/java/org/stepic/droid/adaptive/ui/adapters/AdaptiveRatingAdapter.kt @@ -10,8 +10,8 @@ import android.widget.TextView import androidx.core.content.ContextCompat import androidx.core.widget.ImageViewCompat import androidx.recyclerview.widget.RecyclerView -import kotlinx.android.synthetic.main.adaptive_rating_item.view.* import org.stepic.droid.R +import org.stepic.droid.databinding.AdaptiveRatingItemBinding import org.stepic.droid.preferences.SharedPreferenceHelper import org.stepik.android.model.adaptive.RatingItem @@ -110,11 +110,12 @@ class AdaptiveRatingAdapter( } class RatingViewHolder(val root: View) : RecyclerView.ViewHolder(root) { - val icon: ImageView = root.icon - val rank: TextView = root.rank - val exp: TextView = root.exp - val name: TextView = root.name + private val binding = AdaptiveRatingItemBinding.bind(root) + val icon: ImageView = binding.icon + val rank: TextView = binding.rank + val exp: TextView = binding.exp + val name: TextView = binding.name } class SeparatorViewHolder(view: View) : RecyclerView.ViewHolder(view) -} \ No newline at end of file +} diff --git a/app/src/main/java/org/stepic/droid/adaptive/ui/adapters/AdaptiveWeeksAdapter.kt b/app/src/main/java/org/stepic/droid/adaptive/ui/adapters/AdaptiveWeeksAdapter.kt index 8dbf629d67..d9529cd251 100644 --- a/app/src/main/java/org/stepic/droid/adaptive/ui/adapters/AdaptiveWeeksAdapter.kt +++ b/app/src/main/java/org/stepic/droid/adaptive/ui/adapters/AdaptiveWeeksAdapter.kt @@ -10,10 +10,10 @@ import androidx.recyclerview.widget.RecyclerView import com.github.mikephil.charting.charts.LineChart import com.github.mikephil.charting.data.LineData import com.github.mikephil.charting.data.LineDataSet -import kotlinx.android.synthetic.main.adaptive_header_stats.view.* -import kotlinx.android.synthetic.main.adaptive_item_week.view.* import org.stepic.droid.R import org.stepic.droid.adaptive.model.AdaptiveWeekProgress +import org.stepic.droid.databinding.AdaptiveHeaderStatsBinding +import org.stepic.droid.databinding.AdaptiveItemWeekBinding import org.stepic.droid.util.defaultLocale import org.stepic.droid.util.resolveColorAttribute import java.util.ArrayList @@ -111,16 +111,18 @@ class AdaptiveWeeksAdapter : RecyclerView.Adapter(root) { - private val viewBinding: ViewAchievementItemBinding by viewBinding { ViewAchievementItemBinding.bind(root) } + private val viewBinding: ViewAchievementItemBinding by viewBinding { ViewAchievementItemBinding.bind(itemView) } private val achievementTileDelegate = AchievementTileDelegate(viewBinding.achievementTile.root, achievementResourceResolver) diff --git a/app/src/main/java/org/stepik/android/view/course_revenue/ui/adapter/delegate/CourseBenefitsAdapterDelegate.kt b/app/src/main/java/org/stepik/android/view/course_revenue/ui/adapter/delegate/CourseBenefitsAdapterDelegate.kt index 3db8eab2a2..9be940564d 100644 --- a/app/src/main/java/org/stepik/android/view/course_revenue/ui/adapter/delegate/CourseBenefitsAdapterDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/course_revenue/ui/adapter/delegate/CourseBenefitsAdapterDelegate.kt @@ -32,9 +32,9 @@ class CourseBenefitsAdapterDelegate( ViewHolder(createView(parent, R.layout.item_course_benefit)) private inner class ViewHolder( - override val containerView: View + containerView: View ) : DelegateViewHolder(containerView) { - private val viewBinding: ItemCourseBenefitBinding by viewBinding { ItemCourseBenefitBinding.bind(root) } + private val viewBinding: ItemCourseBenefitBinding by viewBinding { ItemCourseBenefitBinding.bind(itemView) } init { itemView.setOnClickListener { (itemData as? CourseBenefitListItem.Data)?.let { onItemClick(it) } } diff --git a/app/src/main/java/org/stepik/android/view/download/ui/adapter/DownloadedCoursesAdapterDelegate.kt b/app/src/main/java/org/stepik/android/view/download/ui/adapter/DownloadedCoursesAdapterDelegate.kt index 7cb064d169..4b86e3e933 100644 --- a/app/src/main/java/org/stepik/android/view/download/ui/adapter/DownloadedCoursesAdapterDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/download/ui/adapter/DownloadedCoursesAdapterDelegate.kt @@ -25,7 +25,7 @@ class DownloadedCoursesAdapterDelegate( ViewHolder(createView(parent, R.layout.downloaded_course_item)) private inner class ViewHolder(root: View) : DelegateViewHolder(root) { - private val viewBinding: DownloadedCourseItemBinding by viewBinding { DownloadedCourseItemBinding.bind(root) } + private val viewBinding: DownloadedCourseItemBinding by viewBinding { DownloadedCourseItemBinding.bind(itemView) } init { root.setOnClickListener { itemData?.let(onItemClick) } @@ -37,7 +37,7 @@ class DownloadedCoursesAdapterDelegate( } override fun onBind(data: DownloadItem) { - viewBinding.downloadedCourseTitle.text = data.course.title + viewBinding.downloadedCourseName.text = data.course.title viewBinding.downloadedCourseStatus.status = data.status Glide.with(context) diff --git a/app/src/main/res/layout/activity_adaptive_stats.xml b/app/src/main/res/layout/activity_adaptive_stats.xml index 145ba6f3f0..d0010782ee 100644 --- a/app/src/main/res/layout/activity_adaptive_stats.xml +++ b/app/src/main/res/layout/activity_adaptive_stats.xml @@ -4,9 +4,7 @@ android:layout_height="match_parent" android:fitsSystemWindows="true" xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/tools" - tools:viewBindingIgnore="true"> + xmlns:app="http://schemas.android.com/apk/res-auto"> + android:layout_height="wrap_content"> +> +> +> = ViewHolder(createView(parent, R.layout.item_in_app_purchase)) - private inner class ViewHolder(override val containerView: View) : DelegateViewHolder(containerView), LayoutContainer { + private inner class ViewHolder(containerView: View) : DelegateViewHolder(containerView) { + private val viewBinding: ItemInAppPurchaseBinding by viewBinding { ItemInAppPurchaseBinding.bind(itemView) } + init { - inAppPurchaseConsumeAction.setOnClickListener { itemData?.let(onItemClick) } + viewBinding.inAppPurchaseConsumeAction.setOnClickListener { itemData?.let(onItemClick) } } override fun onBind(data: Purchase) { - inAppPurchaseSku.text = data.skus.first() - inAppPurchaseTime.text = context.getString(R.string.debug_purchase_date, DateTimeHelper.getPrintableDate(Date(data.purchaseTime), DateTimeHelper.DISPLAY_DATETIME_PATTERN, TimeZone.getDefault())) - inAppPurchaseStatus.text = context.getString(R.string.debug_purchase_status, data.purchaseState.toString()) + viewBinding.inAppPurchaseSku.text = data.skus.first() + viewBinding.inAppPurchaseTime.text = context.getString(R.string.debug_purchase_date, DateTimeHelper.getPrintableDate(Date(data.purchaseTime), DateTimeHelper.DISPLAY_DATETIME_PATTERN, TimeZone.getDefault())) + viewBinding.inAppPurchaseStatus.text = context.getString(R.string.debug_purchase_status, data.purchaseState.toString()) - inAppPurchaseCourse.isVisible = data.developerPayload.isNotEmpty() - inAppPurchaseUser.isVisible = data.developerPayload.isNotEmpty() + viewBinding.inAppPurchaseCourse.isVisible = data.developerPayload.isNotEmpty() + viewBinding.inAppPurchaseUser.isVisible = data.developerPayload.isNotEmpty() if (data.developerPayload.isNotEmpty()) { data.developerPayload.toObject().let { - inAppPurchaseCourse.text = context.getString(R.string.debug_purchase_course, it.courseId) - inAppPurchaseUser.text = context.getString(R.string.debug_purchase_profile, it.profileId) + viewBinding.inAppPurchaseCourse.text = context.getString(R.string.debug_purchase_course, it.courseId) + viewBinding.inAppPurchaseUser.text = context.getString(R.string.debug_purchase_profile, it.profileId) } } } } -} \ No newline at end of file +} diff --git a/app/src/stageDebuggable/java/org/stepik/android/view/debug/ui/adapter/delegate/SplitTestDataAdapterDelegate.kt b/app/src/stageDebuggable/java/org/stepik/android/view/debug/ui/adapter/delegate/SplitTestDataAdapterDelegate.kt index 6f6bd20719..454b573f13 100644 --- a/app/src/stageDebuggable/java/org/stepik/android/view/debug/ui/adapter/delegate/SplitTestDataAdapterDelegate.kt +++ b/app/src/stageDebuggable/java/org/stepik/android/view/debug/ui/adapter/delegate/SplitTestDataAdapterDelegate.kt @@ -2,9 +2,9 @@ package org.stepik.android.view.debug.ui.adapter.delegate import android.view.View import android.view.ViewGroup -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_split_test_data.* +import by.kirich1409.viewbindingdelegate.viewBinding import org.stepic.droid.R +import org.stepic.droid.databinding.ItemSplitTestDataBinding import org.stepik.android.domain.debug.model.SplitTestData import ru.nobird.android.ui.adapterdelegates.AdapterDelegate import ru.nobird.android.ui.adapterdelegates.DelegateViewHolder @@ -18,13 +18,15 @@ class SplitTestDataAdapterDelegate( override fun onCreateViewHolder(parent: ViewGroup): DelegateViewHolder = ViewHolder(createView(parent, R.layout.item_split_test_data)) - private inner class ViewHolder(override val containerView: View) : DelegateViewHolder(containerView), LayoutContainer { + private inner class ViewHolder(containerView: View) : DelegateViewHolder(containerView) { + private val viewBinding: ItemSplitTestDataBinding by viewBinding { ItemSplitTestDataBinding.bind(itemView) } + init { containerView.setOnClickListener { itemData?.let { onItemClick(it.splitTestName, it.splitTestValue, it.splitTestGroups) } } } override fun onBind(data: SplitTestData) { - splitTestGroupTitle.text = data.splitTestName - splitTestGroupValue.text = data.splitTestValue + viewBinding.splitTestGroupTitle.text = data.splitTestName + viewBinding.splitTestGroupValue.text = data.splitTestValue } } -} \ No newline at end of file +} From 6f828cca4ea494c722328f372a5f68afa817e7af Mon Sep 17 00:00:00 2001 From: Aleksandr Zhukov Date: Tue, 2 Jun 2026 20:00:10 +0200 Subject: [PATCH 05/65] refactor: migrate 4 files from synthetic to viewBinding - AdaptiveLevelDialogFragment.kt (DialogFragment, uses inflate pattern) - AdaptiveOnboardingFragment.kt (Fragment) - AdaptiveRatingFragment.kt (Fragment, includes error_no_connection_with_button) - RecommendationsFragment.kt (Fragment, includes error_no_connection_with_button) Remove viewBindingIgnore from 6 layout XMLs. Add android:id to error layout includes. Co-Authored-By: Claude Opus 4.8 --- .../ui/dialogs/AdaptiveLevelDialogFragment.kt | 15 ++-- .../fragments/AdaptiveOnboardingFragment.kt | 8 +- .../ui/fragments/AdaptiveRatingFragment.kt | 41 ++++----- .../ui/fragments/RecommendationsFragment.kt | 84 ++++++++++--------- .../main/res/layout/dialog_adaptive_level.xml | 3 +- .../error_no_connection_with_button.xml | 3 +- .../layout/fragment_adaptive_onboarding.xml | 4 +- .../res/layout/fragment_adaptive_rating.xml | 8 +- .../res/layout/fragment_recommendations.xml | 7 +- .../main/res/layout/view_centered_appbar.xml | 4 +- 10 files changed, 88 insertions(+), 89 deletions(-) diff --git a/app/src/main/java/org/stepic/droid/adaptive/ui/dialogs/AdaptiveLevelDialogFragment.kt b/app/src/main/java/org/stepic/droid/adaptive/ui/dialogs/AdaptiveLevelDialogFragment.kt index a85225214b..f88d8c579a 100644 --- a/app/src/main/java/org/stepic/droid/adaptive/ui/dialogs/AdaptiveLevelDialogFragment.kt +++ b/app/src/main/java/org/stepic/droid/adaptive/ui/dialogs/AdaptiveLevelDialogFragment.kt @@ -2,16 +2,15 @@ package org.stepic.droid.adaptive.ui.dialogs import android.app.Dialog import android.os.Bundle -import android.view.View import android.view.ViewGroup import androidx.fragment.app.DialogFragment import com.github.jinatonic.confetti.CommonConfetti import com.google.android.material.dialog.MaterialAlertDialogBuilder import io.reactivex.Completable import io.reactivex.Scheduler -import kotlinx.android.synthetic.main.dialog_adaptive_level.view.* import org.stepic.droid.R import org.stepic.droid.base.App +import org.stepic.droid.databinding.DialogAdaptiveLevelBinding import org.stepic.droid.di.qualifiers.MainScheduler import org.stepic.droid.util.resolveColorAttribute import org.stepic.droid.util.resolveFloatAttribute @@ -41,14 +40,14 @@ class AdaptiveLevelDialogFragment : DialogFragment() { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val alertDialogBuilder = MaterialAlertDialogBuilder(requireContext()) - val root = View.inflate(context, R.layout.dialog_adaptive_level, null) - root.adaptiveLevelDialogTitle.text = level.toString() + val binding = DialogAdaptiveLevelBinding.inflate(layoutInflater) + binding.adaptiveLevelDialogTitle.text = level.toString() - root.continueButton.setOnClickListener { dismiss() } + binding.continueButton.setOnClickListener { dismiss() } - expLevelDialogConfetti = root.adaptiveLevelDialogConfetti + expLevelDialogConfetti = binding.adaptiveLevelDialogConfetti - alertDialogBuilder.setView(root) + alertDialogBuilder.setView(binding.root) return alertDialogBuilder.create() } @@ -79,4 +78,4 @@ class AdaptiveLevelDialogFragment : DialogFragment() { .setEmissionRate(15f) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/stepic/droid/adaptive/ui/fragments/AdaptiveOnboardingFragment.kt b/app/src/main/java/org/stepic/droid/adaptive/ui/fragments/AdaptiveOnboardingFragment.kt index 634ecbe95c..0ca607542f 100644 --- a/app/src/main/java/org/stepic/droid/adaptive/ui/fragments/AdaptiveOnboardingFragment.kt +++ b/app/src/main/java/org/stepic/droid/adaptive/ui/fragments/AdaptiveOnboardingFragment.kt @@ -5,11 +5,12 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.annotation.StringRes -import kotlinx.android.synthetic.main.fragment_adaptive_onboarding.* +import by.kirich1409.viewbindingdelegate.viewBinding import org.stepic.droid.R import org.stepic.droid.adaptive.model.Card import org.stepic.droid.adaptive.ui.adapters.OnboardingQuizCardsAdapter import org.stepic.droid.base.FragmentBase +import org.stepic.droid.databinding.FragmentAdaptiveOnboardingBinding import org.stepic.droid.ui.util.initCenteredToolbar import org.stepik.android.model.Block import org.stepik.android.model.Lesson @@ -17,6 +18,7 @@ import org.stepik.android.model.Step import org.stepik.android.model.attempts.Attempt class AdaptiveOnboardingFragment: FragmentBase() { + private val onboardingBinding: FragmentAdaptiveOnboardingBinding by viewBinding(FragmentAdaptiveOnboardingBinding::bind) private val adapter = OnboardingQuizCardsAdapter { if (it == 0) onOnboardingCompleted() } @@ -31,7 +33,7 @@ class AdaptiveOnboardingFragment: FragmentBase() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - cardsContainer.setAdapter(adapter) + onboardingBinding.cardsContainer.setAdapter(adapter) initCenteredToolbar(R.string.adaptive_onboarding_title, showHomeButton = true, homeIndicatorRes = getCloseIconDrawableRes()) } @@ -64,4 +66,4 @@ class AdaptiveOnboardingFragment: FragmentBase() { adapter.destroy() super.onDestroy() } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/stepic/droid/adaptive/ui/fragments/AdaptiveRatingFragment.kt b/app/src/main/java/org/stepic/droid/adaptive/ui/fragments/AdaptiveRatingFragment.kt index 2b3686959f..08ad011c5a 100644 --- a/app/src/main/java/org/stepic/droid/adaptive/ui/fragments/AdaptiveRatingFragment.kt +++ b/app/src/main/java/org/stepic/droid/adaptive/ui/fragments/AdaptiveRatingFragment.kt @@ -9,14 +9,14 @@ import android.widget.ArrayAdapter import androidx.core.content.ContextCompat import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager -import kotlinx.android.synthetic.main.error_no_connection_with_button.* -import kotlinx.android.synthetic.main.fragment_adaptive_rating.* +import by.kirich1409.viewbindingdelegate.viewBinding import org.stepic.droid.R import org.stepic.droid.adaptive.ui.adapters.AdaptiveRatingAdapter import org.stepic.droid.base.App import org.stepic.droid.base.FragmentBase import org.stepic.droid.core.presenters.AdaptiveRatingPresenter import org.stepic.droid.core.presenters.contracts.AdaptiveRatingView +import org.stepic.droid.databinding.FragmentAdaptiveRatingBinding import ru.nobird.android.view.base.ui.extension.argument import javax.inject.Inject @@ -30,6 +30,7 @@ class AdaptiveRatingFragment: FragmentBase(), AdaptiveRatingView { @Inject lateinit var adaptiveRatingPresenter: AdaptiveRatingPresenter + private val adaptiveRatingBinding: FragmentAdaptiveRatingBinding by viewBinding(FragmentAdaptiveRatingBinding::bind) private var courseId by argument() override fun injectComponent() { @@ -45,17 +46,17 @@ class AdaptiveRatingFragment: FragmentBase(), AdaptiveRatingView { val context = requireContext() super.onViewCreated(view, savedInstanceState) - recycler.layoutManager = LinearLayoutManager(context) + adaptiveRatingBinding.recycler.layoutManager = LinearLayoutManager(context) val divider = DividerItemDecoration(context, DividerItemDecoration.VERTICAL) divider.setDrawable(ContextCompat.getDrawable(context, R.drawable.bg_divider_vertical)!!) - recycler.addItemDecoration(divider) + adaptiveRatingBinding.recycler.addItemDecoration(divider) val spinnerAdapter = ArrayAdapter(context, R.layout.adaptive_item_rating_period, context.resources.getStringArray(R.array.adaptive_rating_periods)) spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) - spinner.adapter = spinnerAdapter + adaptiveRatingBinding.spinner.adapter = spinnerAdapter - spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + adaptiveRatingBinding.spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { override fun onNothingSelected(p0: AdapterView<*>?) {} override fun onItemSelected(p0: AdapterView<*>?, p1: View?, pos: Int, p3: Long) { @@ -63,39 +64,39 @@ class AdaptiveRatingFragment: FragmentBase(), AdaptiveRatingView { } } - tryAgain.setOnClickListener { adaptiveRatingPresenter.retry() } + adaptiveRatingBinding.errorNoConnectionWithButton.tryAgain.setOnClickListener { adaptiveRatingPresenter.retry() } } override fun onLoading() { - error.visibility = View.GONE - progress.visibility = View.VISIBLE - container.visibility = View.GONE + adaptiveRatingBinding.errorNoConnectionWithButton.error.visibility = View.GONE + adaptiveRatingBinding.progress.visibility = View.VISIBLE + adaptiveRatingBinding.container.visibility = View.GONE } private fun onError() { - error.visibility = View.VISIBLE - progress.visibility = View.GONE - container.visibility = View.GONE + adaptiveRatingBinding.errorNoConnectionWithButton.error.visibility = View.VISIBLE + adaptiveRatingBinding.progress.visibility = View.GONE + adaptiveRatingBinding.container.visibility = View.GONE } override fun onConnectivityError() { - errorMessage.setText(R.string.no_connection) + adaptiveRatingBinding.errorNoConnectionWithButton.errorMessage.setText(R.string.no_connection) onError() } override fun onRequestError() { - errorMessage.setText(R.string.request_error) + adaptiveRatingBinding.errorNoConnectionWithButton.errorMessage.setText(R.string.request_error) onError() } override fun onComplete() { - error.visibility = View.GONE - progress.visibility = View.GONE - container.visibility = View.VISIBLE + adaptiveRatingBinding.errorNoConnectionWithButton.error.visibility = View.GONE + adaptiveRatingBinding.progress.visibility = View.GONE + adaptiveRatingBinding.container.visibility = View.VISIBLE } override fun onRatingAdapter(adapter: AdaptiveRatingAdapter) { - recycler.adapter = adapter + adaptiveRatingBinding.recycler.adapter = adapter } override fun onStart() { @@ -117,4 +118,4 @@ class AdaptiveRatingFragment: FragmentBase(), AdaptiveRatingView { adaptiveRatingPresenter.destroy() super.onDestroy() } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/stepic/droid/adaptive/ui/fragments/RecommendationsFragment.kt b/app/src/main/java/org/stepic/droid/adaptive/ui/fragments/RecommendationsFragment.kt index 22e3a6337b..57aadc566c 100644 --- a/app/src/main/java/org/stepic/droid/adaptive/ui/fragments/RecommendationsFragment.kt +++ b/app/src/main/java/org/stepic/droid/adaptive/ui/fragments/RecommendationsFragment.kt @@ -7,8 +7,7 @@ import android.view.ViewGroup import android.widget.PopupWindow import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat -import kotlinx.android.synthetic.main.error_no_connection_with_button.* -import kotlinx.android.synthetic.main.fragment_recommendations.* +import by.kirich1409.viewbindingdelegate.viewBinding import org.stepic.droid.R import org.stepic.droid.adaptive.ui.adapters.QuizCardsAdapter import org.stepic.droid.adaptive.ui.animations.RecommendationsFragmentAnimations @@ -17,6 +16,7 @@ import org.stepic.droid.base.App import org.stepic.droid.base.FragmentBase import org.stepic.droid.core.presenters.RecommendationsPresenter import org.stepic.droid.core.presenters.contracts.RecommendationsView +import org.stepic.droid.databinding.FragmentRecommendationsBinding import org.stepic.droid.ui.util.PopupHelper import org.stepic.droid.util.AppConstants import org.stepic.droid.util.resolveColorAttribute @@ -46,6 +46,8 @@ class RecommendationsFragment : FragmentBase(), RecommendationsView { private lateinit var animations: RecommendationsFragmentAnimations + private val recommendationsBinding: FragmentRecommendationsBinding by viewBinding(FragmentRecommendationsBinding::bind) + private var course: Course? = null private val loadingPlaceholders by lazy { resources.getStringArray(R.array.recommendation_loading_placeholders) } @@ -71,21 +73,21 @@ class RecommendationsFragment : FragmentBase(), RecommendationsView { val context = requireContext() - animations = RecommendationsFragmentAnimations(streakSuccessContainer.context) + animations = RecommendationsFragmentAnimations(recommendationsBinding.streakSuccessContainer.context) - error.setBackgroundColor(0) + recommendationsBinding.errorNoConnectionWithButton.error.setBackgroundColor(0) - tryAgain.setOnClickListener { + recommendationsBinding.errorNoConnectionWithButton.tryAgain.setOnClickListener { recommendationsPresenter.retry() } (activity as? AppCompatActivity)?.let { - it.setSupportActionBar(toolbar) + it.setSupportActionBar(recommendationsBinding.toolbar) it.supportActionBar?.setDisplayHomeAsUpEnabled(true) it.supportActionBar?.setDisplayShowTitleEnabled(false) } - toolbar.setOnClickListener { + recommendationsBinding.toolbar.setOnClickListener { screenManager.showAdaptiveStats(context, course?.id ?: 0) expPopupWindow?.let { popup -> @@ -95,54 +97,54 @@ class RecommendationsFragment : FragmentBase(), RecommendationsView { } } - streakSuccessContainer.nestedTextView = streakSuccess - streakSuccessContainer.setGradientDrawableParams(streakSuccessContainer.context.resolveColorAttribute(R.attr.colorPrimary), 0f) + recommendationsBinding.streakSuccessContainer.nestedTextView = recommendationsBinding.streakSuccess + recommendationsBinding.streakSuccessContainer.setGradientDrawableParams(recommendationsBinding.streakSuccessContainer.context.resolveColorAttribute(R.attr.colorPrimary), 0f) } override fun onAdapter(cardsAdapter: QuizCardsAdapter) { - cardsContainer.setAdapter(cardsAdapter) + recommendationsBinding.cardsContainer.setAdapter(cardsAdapter) } override fun onLoading() { - progress.visibility = View.VISIBLE - error.visibility = View.GONE - loadingPlaceholder.text = loadingPlaceholders.random() + recommendationsBinding.progress.visibility = View.VISIBLE + recommendationsBinding.errorNoConnectionWithButton.error.visibility = View.GONE + recommendationsBinding.loadingPlaceholder.text = loadingPlaceholders.random() } override fun onCardLoaded() { - progress.visibility = View.GONE - cardsContainer.visibility = View.VISIBLE + recommendationsBinding.progress.visibility = View.GONE + recommendationsBinding.cardsContainer.visibility = View.VISIBLE } private fun onError() { - cardsContainer.visibility = View.GONE - error.visibility = View.VISIBLE - progress.visibility = View.GONE + recommendationsBinding.cardsContainer.visibility = View.GONE + recommendationsBinding.errorNoConnectionWithButton.error.visibility = View.VISIBLE + recommendationsBinding.progress.visibility = View.GONE } override fun onConnectivityError() { - errorMessage.setText(R.string.no_connection) + recommendationsBinding.errorNoConnectionWithButton.errorMessage.setText(R.string.no_connection) onError() } override fun onRequestError() { - errorMessage.setText(R.string.request_error) + recommendationsBinding.errorNoConnectionWithButton.errorMessage.setText(R.string.request_error) onError() } private fun onCourseState() { - cardsContainer.visibility = View.GONE - progress.visibility = View.GONE - courseState.visibility = View.VISIBLE + recommendationsBinding.cardsContainer.visibility = View.GONE + recommendationsBinding.progress.visibility = View.GONE + recommendationsBinding.courseState.visibility = View.VISIBLE } override fun onCourseCompleted() { - courseStateText.setText(R.string.adaptive_course_completed) + recommendationsBinding.courseStateText.setText(R.string.adaptive_course_completed) onCourseState() } override fun onCourseNotSupported() { - courseStateText.setText(R.string.adaptive_course_not_supported) + recommendationsBinding.courseStateText.setText(R.string.adaptive_course_not_supported) onCourseState() } @@ -153,37 +155,37 @@ class RecommendationsFragment : FragmentBase(), RecommendationsView { level: Long ) { - expProgress.progress = (exp - currentLevelExp).toInt() - expProgress.max = (nextLevelExp - currentLevelExp).toInt() + recommendationsBinding.expProgress.progress = (exp - currentLevelExp).toInt() + recommendationsBinding.expProgress.max = (nextLevelExp - currentLevelExp).toInt() - expCounter.text = formatExp(exp) - expLevel.text = getString(R.string.adaptive_exp_title, level) - expLevelNext.text = getString(R.string.adaptive_exp_subtitle, formatExp(nextLevelExp - exp)) + recommendationsBinding.expCounter.text = formatExp(exp) + recommendationsBinding.expLevel.text = getString(R.string.adaptive_exp_title, level) + recommendationsBinding.expLevelNext.text = getString(R.string.adaptive_exp_subtitle, formatExp(nextLevelExp - exp)) } override fun onStreak(streak: Long) { - expInc.text = getString(R.string.adaptive_exp_inc, streak) - streakSuccess.text = resources.getQuantityString(R.plurals.adaptive_streak_success, streak.toInt(), streak) + recommendationsBinding.expInc.text = getString(R.string.adaptive_exp_inc, streak) + recommendationsBinding.streakSuccess.text = resources.getQuantityString(R.plurals.adaptive_streak_success, streak.toInt(), streak) if (streak > 1) { animations.playStreakSuccessAnimationSequence( - root = rootView, - streakSuccessContainer = streakSuccessContainer, - expProgress = expProgress, - expInc = expInc, - expBubble = expBubble + root = recommendationsBinding.rootView, + streakSuccessContainer = recommendationsBinding.streakSuccessContainer, + expProgress = recommendationsBinding.expProgress, + expInc = recommendationsBinding.expInc, + expBubble = recommendationsBinding.expBubble ) } else { - animations.playStreakBubbleAnimation(expInc) + animations.playStreakBubbleAnimation(recommendationsBinding.expInc) } } override fun showExpTooltip() { - expPopupWindow = PopupHelper.showPopupAnchoredToView(requireContext(), expBubble, getString(R.string.adaptive_exp_tooltip_text), withArrow = true) + expPopupWindow = PopupHelper.showPopupAnchoredToView(requireContext(), recommendationsBinding.expBubble, getString(R.string.adaptive_exp_tooltip_text), withArrow = true) } override fun onStreakLost() { - animations.playStreakFailedAnimation(streakFailed, expProgress) + animations.playStreakFailedAnimation(recommendationsBinding.streakFailed, recommendationsBinding.expProgress) } override fun showNewLevelDialog(level: Long) { @@ -211,4 +213,4 @@ class RecommendationsFragment : FragmentBase(), RecommendationsView { recommendationsPresenter.destroy() super.onDestroy() } -} \ No newline at end of file +} diff --git a/app/src/main/res/layout/dialog_adaptive_level.xml b/app/src/main/res/layout/dialog_adaptive_level.xml index 5a7737445d..731ad87f63 100644 --- a/app/src/main/res/layout/dialog_adaptive_level.xml +++ b/app/src/main/res/layout/dialog_adaptive_level.xml @@ -4,8 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:orientation="vertical" android:layout_width="match_parent" - android:layout_height="wrap_content" - tools:viewBindingIgnore="true"> + android:layout_height="wrap_content"> + tools:visibility="visible"> + xmlns:app="http://schemas.android.com/apk/res-auto"> + xmlns:android="http://schemas.android.com/apk/res/android"> - + + android:id="@+id/rootView"> - + \ No newline at end of file diff --git a/app/src/main/res/layout/view_centered_appbar.xml b/app/src/main/res/layout/view_centered_appbar.xml index f1dc2d4541..ccbd6f9684 100644 --- a/app/src/main/res/layout/view_centered_appbar.xml +++ b/app/src/main/res/layout/view_centered_appbar.xml @@ -1,11 +1,9 @@ + android:elevation="?appBarElevation"> From 6ff0cb2cdb92bea447a576f10cd283b3d8c83484 Mon Sep 17 00:00:00 2001 From: Aleksandr Zhukov Date: Tue, 2 Jun 2026 20:06:00 +0200 Subject: [PATCH 06/65] refactor: migrate 4 files from synthetic to viewBinding - StoriesAdapter.kt (RecyclerView.ViewHolder pattern) - FeedbackStoryPartDelegate.kt (delegate, programmatic inflation, includes view_story_text_input) - PlainTextWithButtonStoryPartDelegate.kt (delegate, programmatic inflation) - StoriesActivityDelegate.kt (Activity delegate, binds activity content view) Remove viewBindingIgnore from 5 layout XMLs. Co-Authored-By: Claude Opus 4.8 --- .../stories/ui/adapter/StoriesAdapter.kt | 11 +-- .../ui/delegate/FeedbackStoryPartDelegate.kt | 83 +++++++++---------- .../PlainTextWithButtonStoryPartDelegate.kt | 74 ++++++++--------- .../ui/delegate/StoriesActivityDelegate.kt | 14 ++-- app/src/main/res/layout/activity_stories.xml | 2 +- .../main/res/layout/view_story_feedback.xml | 1 - app/src/main/res/layout/view_story_item.xml | 4 +- .../view_story_plain_text_with_button.xml | 1 - .../main/res/layout/view_story_text_input.xml | 2 +- 9 files changed, 95 insertions(+), 97 deletions(-) diff --git a/app/src/main/java/org/stepic/droid/features/stories/ui/adapter/StoriesAdapter.kt b/app/src/main/java/org/stepic/droid/features/stories/ui/adapter/StoriesAdapter.kt index 2da7a29263..c5e724e7f1 100644 --- a/app/src/main/java/org/stepic/droid/features/stories/ui/adapter/StoriesAdapter.kt +++ b/app/src/main/java/org/stepic/droid/features/stories/ui/adapter/StoriesAdapter.kt @@ -8,8 +8,8 @@ import androidx.core.view.isInvisible import androidx.recyclerview.widget.RecyclerView import com.bumptech.glide.Glide import com.google.android.material.imageview.ShapeableImageView -import kotlinx.android.synthetic.main.view_story_item.view.* import org.stepic.droid.R +import org.stepic.droid.databinding.ViewStoryItemBinding import ru.nobird.android.stories.model.Story import kotlin.properties.Delegates @@ -66,9 +66,10 @@ class StoriesAdapter( } inner class StoryViewHolder(root: View) : RecyclerView.ViewHolder(root) { - val cover: ShapeableImageView = root.storyCover - private val title = root.storyTitle - private val activeStoryMarker = root.activeStoryMarker + private val binding = ViewStoryItemBinding.bind(root) + val cover: ShapeableImageView = binding.storyCover + private val title = binding.storyTitle + private val activeStoryMarker = binding.activeStoryMarker init { root.setOnClickListener { @@ -93,4 +94,4 @@ class StoriesAdapter( itemView.isInvisible = position == selected } } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/stepic/droid/features/stories/ui/delegate/FeedbackStoryPartDelegate.kt b/app/src/main/java/org/stepic/droid/features/stories/ui/delegate/FeedbackStoryPartDelegate.kt index 807c1d1f78..d329e39869 100644 --- a/app/src/main/java/org/stepic/droid/features/stories/ui/delegate/FeedbackStoryPartDelegate.kt +++ b/app/src/main/java/org/stepic/droid/features/stories/ui/delegate/FeedbackStoryPartDelegate.kt @@ -16,11 +16,10 @@ import androidx.core.view.isVisible import androidx.core.widget.TextViewCompat import androidx.swiperefreshlayout.widget.CircularProgressDrawable import com.bumptech.glide.Glide -import kotlinx.android.synthetic.main.view_story_feedback.view.* -import kotlinx.android.synthetic.main.view_story_text_input.view.* import org.stepic.droid.R import org.stepic.droid.analytic.AmplitudeAnalytic import org.stepic.droid.analytic.Analytic +import org.stepic.droid.databinding.ViewStoryFeedbackBinding import org.stepic.droid.features.stories.model.FeedbackStoryPart import org.stepic.droid.ui.util.setOnKeyboardOpenListener import org.stepik.android.model.StoryTemplate @@ -50,42 +49,42 @@ class FeedbackStoryPartDelegate( override fun isForViewType(part: StoryPart): Boolean = part is FeedbackStoryPart - override fun onBindView(storyView: StoryView, container: ViewGroup, position: Int, part: StoryPart): View = - container.inflate(R.layout.view_story_feedback, false).apply { - part as FeedbackStoryPart - (context as? AppCompatActivity)?.currentFocus?.clearFocus() - - Glide.with(context) - .load(part.cover) - .placeholder(progressDrawable) - .into(this.storyCover) - - val story = storyView.adapter?.story - if (story != null) { - analytic.reportAmplitudeEvent( - AmplitudeAnalytic.Stories.STORY_PART_OPENED, mapOf( - AmplitudeAnalytic.Stories.Values.STORY_ID to story.id, - AmplitudeAnalytic.Stories.Values.POSITION to position - )) - } - - setUpText(this, part.text) - setUpButton(story, this, part.button, position) - setUpInput(this, storyView, part.feedback) + override fun onBindView(storyView: StoryView, container: ViewGroup, position: Int, part: StoryPart): View { + val binding = ViewStoryFeedbackBinding.inflate(LayoutInflater.from(context), container, false) + part as FeedbackStoryPart + (context as? AppCompatActivity)?.currentFocus?.clearFocus() + + Glide.with(context) + .load(part.cover) + .placeholder(progressDrawable) + .into(binding.storyCover) + + val story = storyView.adapter?.story + if (story != null) { + analytic.reportAmplitudeEvent( + AmplitudeAnalytic.Stories.STORY_PART_OPENED, mapOf( + AmplitudeAnalytic.Stories.Values.STORY_ID to story.id, + AmplitudeAnalytic.Stories.Values.POSITION to position + )) } - private fun setUpText(view: View, text: StoryTemplate.Text?) { + setUpText(binding, part.text) + setUpButton(story, binding, part.button, position) + setUpInput(binding, storyView, part.feedback) + return binding.root + } + + private fun setUpText(binding: ViewStoryFeedbackBinding, text: StoryTemplate.Text?) { if (text != null) { - val storyTitle = view.storyTitle @ColorInt val textColor = getColorInt(text.textColor) - storyTitle.setTextColor(textColor) - storyTitle.text = text.title + binding.storyTitle.setTextColor(textColor) + binding.storyTitle.text = text.title } } - private fun setUpButton(story: Story?, view: View, button: StoryTemplate.Button?, position: Int) { - val storyButton = view.storyButton - val storyFeedbackEditText = view.storyFeedbackEditText + private fun setUpButton(story: Story?, binding: ViewStoryFeedbackBinding, button: StoryTemplate.Button?, position: Int) { + val storyButton = binding.storyButton + val storyFeedbackEditText = binding.storyInputContainer.storyFeedbackEditText if (button != null) { ViewCompat.setBackgroundTintList(storyButton, ColorStateList.valueOf(getColorInt(button.backgroundColor))) storyButton.setTextColor(getColorInt(button.textColor)) @@ -114,15 +113,15 @@ class FeedbackStoryPartDelegate( } } - private fun setUpInput(view: View, storyView: StoryView, feedback: StoryTemplate.Feedback?) { + private fun setUpInput(binding: ViewStoryFeedbackBinding, storyView: StoryView, feedback: StoryTemplate.Feedback?) { if (feedback == null) return - val title = view.storyTitle - val storyFeedbackContainer = view.storyInputContainer - val storyFeedbackText = view.storyFeedbackText - val storyFeedbackIcon = view.storyFeedbackIcon - val storyFeedbackEditText = view.storyFeedbackEditText + val title = binding.storyTitle + val storyInputBinding = binding.storyInputContainer + val storyFeedbackText = storyInputBinding.storyFeedbackText + val storyFeedbackIcon = storyInputBinding.storyFeedbackIcon + val storyFeedbackEditText = storyInputBinding.storyFeedbackEditText - storyFeedbackContainer.background = getColoredDrawable(R.drawable.bg_shape_rounded, feedback.backgroundColor) + storyInputBinding.root.background = getColoredDrawable(R.drawable.bg_shape_rounded, feedback.backgroundColor) storyFeedbackText.text = feedback.text storyFeedbackText.setTextColor(getColorInt(feedback.textColor)) @@ -140,9 +139,9 @@ class FeedbackStoryPartDelegate( storyFeedbackEditText.setHintTextColor(getColorInt(feedback.placeholderTextColor)) storyFeedbackEditText.setOnFocusChangeListener { _, hasFocus -> - view.isFocusableInTouchMode = hasFocus - view.isFocusable = hasFocus - view.isClickable = hasFocus + binding.root.isFocusableInTouchMode = hasFocus + binding.root.isFocusable = hasFocus + binding.root.isClickable = hasFocus storyFeedbackEditText.post { dismissableLayout.isFocusable = !hasFocus dismissableLayout.isFocusableInTouchMode = !hasFocus @@ -181,4 +180,4 @@ class FeedbackStoryPartDelegate( */ private fun getColorInt(color: String): Int = "#$color".toColorInt() -} \ No newline at end of file +} diff --git a/app/src/main/java/org/stepic/droid/features/stories/ui/delegate/PlainTextWithButtonStoryPartDelegate.kt b/app/src/main/java/org/stepic/droid/features/stories/ui/delegate/PlainTextWithButtonStoryPartDelegate.kt index b9fb91869a..9c42e63f06 100644 --- a/app/src/main/java/org/stepic/droid/features/stories/ui/delegate/PlainTextWithButtonStoryPartDelegate.kt +++ b/app/src/main/java/org/stepic/droid/features/stories/ui/delegate/PlainTextWithButtonStoryPartDelegate.kt @@ -3,6 +3,7 @@ package org.stepic.droid.features.stories.ui.delegate import android.content.Context import android.content.res.ColorStateList import android.net.Uri +import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.annotation.ColorInt @@ -11,10 +12,10 @@ import androidx.core.view.ViewCompat import androidx.core.view.isVisible import androidx.swiperefreshlayout.widget.CircularProgressDrawable import com.bumptech.glide.Glide -import kotlinx.android.synthetic.main.view_story_plain_text_with_button.view.* import org.stepic.droid.R import org.stepic.droid.analytic.AmplitudeAnalytic import org.stepic.droid.analytic.Analytic +import org.stepic.droid.databinding.ViewStoryPlainTextWithButtonBinding import org.stepic.droid.features.stories.model.PlainTextWithButtonStoryPart import org.stepik.android.domain.story.model.StoryReaction import org.stepik.android.model.StoryTemplate @@ -23,7 +24,6 @@ import ru.nobird.android.stories.model.Story import ru.nobird.android.stories.model.StoryPart import ru.nobird.android.stories.ui.custom.StoryView import ru.nobird.android.stories.ui.delegate.StoryPartViewDelegate -import ru.nobird.android.view.base.ui.extension.inflate class PlainTextWithButtonStoryPartDelegate( private val analytic: Analytic, @@ -47,47 +47,45 @@ class PlainTextWithButtonStoryPartDelegate( override fun isForViewType(part: StoryPart): Boolean = part is PlainTextWithButtonStoryPart - override fun onBindView(storyView: StoryView, container: ViewGroup, position: Int, part: StoryPart): View = - container.inflate(R.layout.view_story_plain_text_with_button, false).apply { - part as PlainTextWithButtonStoryPart - (context as? AppCompatActivity)?.currentFocus?.clearFocus() - - Glide.with(context) - .load(part.cover) - .placeholder(progressDrawable) - .into(this.storyCover) - - val story = storyView.adapter?.story - if (story != null) { - analytic.reportAmplitudeEvent(AmplitudeAnalytic.Stories.STORY_PART_OPENED, mapOf( - AmplitudeAnalytic.Stories.Values.STORY_ID to story.id, - AmplitudeAnalytic.Stories.Values.POSITION to position - )) - } - - setUpText(this, part.text) - setUpButton(story, this, part.button, position) - setUpReactions(story, this, position) + override fun onBindView(storyView: StoryView, container: ViewGroup, position: Int, part: StoryPart): View { + val binding = ViewStoryPlainTextWithButtonBinding.inflate(LayoutInflater.from(context), container, false) + part as PlainTextWithButtonStoryPart + (context as? AppCompatActivity)?.currentFocus?.clearFocus() + + Glide.with(context) + .load(part.cover) + .placeholder(progressDrawable) + .into(binding.storyCover) + + val story = storyView.adapter?.story + if (story != null) { + analytic.reportAmplitudeEvent(AmplitudeAnalytic.Stories.STORY_PART_OPENED, mapOf( + AmplitudeAnalytic.Stories.Values.STORY_ID to story.id, + AmplitudeAnalytic.Stories.Values.POSITION to position + )) } - private fun setUpText(view: View, text: StoryTemplate.Text?) { - if (text != null) { - val storyTitle = view.storyTitle - val storyText = view.storyText + setUpText(binding, part.text) + setUpButton(story, binding, part.button, position) + setUpReactions(story, binding, position) + return binding.root + } + private fun setUpText(binding: ViewStoryPlainTextWithButtonBinding, text: StoryTemplate.Text?) { + if (text != null) { @ColorInt val textColor = COLOR_MASK or text.textColor.toInt(16) - storyTitle.setTextColor(textColor) - storyText.setTextColor(textColor) + binding.storyTitle.setTextColor(textColor) + binding.storyText.setTextColor(textColor) - storyTitle.text = text.title - storyText.text = text.text - storyText.isVisible = text.text?.isNotBlank() ?: false + binding.storyTitle.text = text.title + binding.storyText.text = text.text + binding.storyText.isVisible = text.text?.isNotBlank() ?: false } } - private fun setUpButton(story: Story?, view: View, button: StoryTemplate.Button?, position: Int) { - val storyButton = view.storyButton + private fun setUpButton(story: Story?, binding: ViewStoryPlainTextWithButtonBinding, button: StoryTemplate.Button?, position: Int) { + val storyButton = binding.storyButton if (button != null) { ViewCompat.setBackgroundTintList(storyButton, ColorStateList.valueOf(COLOR_MASK or button.backgroundColor.toInt(16))) storyButton.setTextColor(COLOR_MASK or button.textColor.toInt(16)) @@ -111,18 +109,18 @@ class PlainTextWithButtonStoryPartDelegate( } } - fun setUpReactions(story: Story?, view: View, position: Int) { + fun setUpReactions(story: Story?, binding: ViewStoryPlainTextWithButtonBinding, position: Int) { val storyId = story?.id ?: 0 val vote = storyReactions[storyId] - with(view.storyReactionLike) { + with(binding.storyReactionLike) { setOnClickListener { val id = story?.id ?: return@setOnClickListener storyReactionListener.invoke(id, position, StoryReaction.LIKE) } isActivated = vote == StoryReaction.LIKE } - with(view.storyReactionDislike) { + with(binding.storyReactionDislike) { setOnClickListener { val id = story?.id ?: return@setOnClickListener storyReactionListener.invoke(id, position, StoryReaction.DISLIKE) @@ -130,4 +128,4 @@ class PlainTextWithButtonStoryPartDelegate( isActivated = vote == StoryReaction.DISLIKE } } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/stepic/droid/features/stories/ui/delegate/StoriesActivityDelegate.kt b/app/src/main/java/org/stepic/droid/features/stories/ui/delegate/StoriesActivityDelegate.kt index 5b8c9063a4..1a53d6a527 100644 --- a/app/src/main/java/org/stepic/droid/features/stories/ui/delegate/StoriesActivityDelegate.kt +++ b/app/src/main/java/org/stepic/droid/features/stories/ui/delegate/StoriesActivityDelegate.kt @@ -4,10 +4,11 @@ import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.core.view.children import androidx.viewpager.widget.ViewPager -import kotlinx.android.synthetic.main.activity_stories.* import org.stepic.droid.R import org.stepic.droid.analytic.AmplitudeAnalytic import org.stepic.droid.analytic.Analytic +import org.stepic.droid.databinding.ActivityStoriesBinding +import org.stepic.droid.databinding.ViewStoryPlainTextWithButtonBinding import org.stepik.android.domain.story.model.StoryReaction import ru.nobird.app.core.model.safeCast import ru.nobird.android.stories.model.Story @@ -22,15 +23,17 @@ class StoriesActivityDelegate( private val analytic: Analytic, storyReactionListener: (storyId: Long, storyPosition: Int, storyReaction: StoryReaction) -> Unit ) : StoriesActivityDelegateBase(activity) { + private val activityBinding = ActivityStoriesBinding.bind(activity.findViewById(R.id.content)) + private val storyReactions = mutableMapOf() private val storyPartDelegate = PlainTextWithButtonStoryPartDelegate(analytic, activity, storyReactions, storyReactionListener) public override val dismissableLayout: DismissableLayout = - activity.content + activityBinding.content public override val storiesViewPager: ViewPager = - activity.storiesPager + activityBinding.storiesPager override val arguments: Bundle = activity.intent.extras ?: Bundle.EMPTY @@ -76,8 +79,9 @@ class StoriesActivityDelegate( ?.findViewById(R.id.storyViewPager) storyPartPager?.children?.forEach { view -> - storyPartDelegate.setUpReactions(story, view, position) + val binding = ViewStoryPlainTextWithButtonBinding.bind(view) + storyPartDelegate.setUpReactions(story, binding, position) } } } -} \ No newline at end of file +} diff --git a/app/src/main/res/layout/activity_stories.xml b/app/src/main/res/layout/activity_stories.xml index 204e3995f2..7c8a5b0018 100644 --- a/app/src/main/res/layout/activity_stories.xml +++ b/app/src/main/res/layout/activity_stories.xml @@ -6,7 +6,7 @@ android:layout_alignParentTop="true" android:layout_width="match_parent" android:layout_height="match_parent" - tools:viewBindingIgnore="true"> +> + android:foreground="@drawable/story_view_foreground"> + tools:context=".features.stories.ui.activity.StoriesActivity"> Date: Tue, 2 Jun 2026 20:14:51 +0200 Subject: [PATCH 07/65] refactor: migrate 8 files from synthetic to viewBinding - AnimatedOnboardingActivity.kt (Activity) - PhotoViewActivity.kt (Activity) - SearchQueriesAdapter.kt (RecyclerView.ViewHolder) - SocialLinksAdapter.kt (RecyclerView.ViewHolder) - AboutAppFragment.kt (Fragment) - FeedbackFragment.kt (Fragment) - NotificationSettingsFragment.kt (Fragment) - OnboardingFragment.kt (Fragment) - StoreManagementFragment.kt (Jetpack Fragment) Remove viewBindingIgnore from 14 layout XMLs. Co-Authored-By: Claude Opus 4.8 --- .../activities/AnimatedOnboardingActivity.kt | 34 +++++++------- .../droid/ui/activities/PhotoViewActivity.kt | 36 +++++++------- .../droid/ui/adapters/SearchQueriesAdapter.kt | 13 ++--- .../droid/ui/adapters/SocialLinksAdapter.kt | 7 +-- .../droid/ui/fragments/AboutAppFragment.kt | 18 +++---- .../droid/ui/fragments/FeedbackFragment.kt | 14 +++--- .../fragments/NotificationSettingsFragment.kt | 47 ++++++++++--------- .../droid/ui/fragments/OnboardingFragment.kt | 25 +++++----- .../ui/fragments/StoreManagementFragment.kt | 47 ++++++++++--------- .../main/res/layout/activity_main_feed.xml | 4 +- .../main/res/layout/activity_onboarding.xml | 4 +- .../main/res/layout/fragment_about_app.xml | 3 +- app/src/main/res/layout/fragment_feedback.xml | 4 +- app/src/main/res/layout/fragment_home.xml | 4 +- .../layout/fragment_notification_settings.xml | 4 +- .../res/layout/fragment_onboarding_page.xml | 1 - .../main/res/layout/fragment_photo_view.xml | 4 +- .../res/layout/fragment_space_management.xml | 3 +- app/src/main/res/layout/home_streak_view.xml | 3 +- app/src/main/res/layout/item_social.xml | 3 +- app/src/main/res/layout/search_query_item.xml | 3 +- .../main/res/layout/view_centered_toolbar.xml | 4 +- 22 files changed, 142 insertions(+), 143 deletions(-) diff --git a/app/src/main/java/org/stepic/droid/ui/activities/AnimatedOnboardingActivity.kt b/app/src/main/java/org/stepic/droid/ui/activities/AnimatedOnboardingActivity.kt index 462166dc8d..a25c85ac48 100644 --- a/app/src/main/java/org/stepic/droid/ui/activities/AnimatedOnboardingActivity.kt +++ b/app/src/main/java/org/stepic/droid/ui/activities/AnimatedOnboardingActivity.kt @@ -3,7 +3,7 @@ package org.stepic.droid.ui.activities import android.content.pm.ActivityInfo import android.os.Bundle import androidx.viewpager.widget.ViewPager -import kotlinx.android.synthetic.main.activity_onboarding.* +import by.kirich1409.viewbindingdelegate.viewBinding import org.stepic.droid.R import org.stepic.droid.analytic.AmplitudeAnalytic import org.stepic.droid.analytic.Analytic @@ -11,6 +11,7 @@ import org.stepic.droid.analytic.experiments.DeferredAuthSplitTest import org.stepic.droid.analytic.experiments.OnboardingSplitTestVersion2 import org.stepic.droid.base.App import org.stepic.droid.base.FragmentActivityBase +import org.stepic.droid.databinding.ActivityOnboardingBinding import org.stepic.droid.ui.activities.contracts.OnNextClickedListener import org.stepic.droid.ui.adapters.OnboardingAdapter import org.stepic.droid.ui.custom.OnboardingPageTransformer @@ -18,6 +19,7 @@ import org.stepic.droid.ui.fragments.OnboardingFragment import javax.inject.Inject class AnimatedOnboardingActivity : FragmentActivityBase(), OnNextClickedListener { + private val onboardingBinding: ActivityOnboardingBinding by viewBinding(ActivityOnboardingBinding::bind) @Inject lateinit var deferredAuthSplitsTest: DeferredAuthSplitTest @@ -33,10 +35,10 @@ class AnimatedOnboardingActivity : FragmentActivityBase(), OnNextClickedListener private fun initViewPager() { val onboardingAdapter = OnboardingAdapter(supportFragmentManager) - onboardingViewPager.adapter = onboardingAdapter - onboardingViewPager.offscreenPageLimit = onboardingAdapter.count + onboardingBinding.onboardingViewPager.adapter = onboardingAdapter + onboardingBinding.onboardingViewPager.offscreenPageLimit = onboardingAdapter.count - onboardingCircleIndicator.setViewPager(onboardingViewPager) + onboardingBinding.onboardingCircleIndicator.setViewPager(onboardingBinding.onboardingViewPager) val pageChangeListener: ViewPager.OnPageChangeListener = object : ViewPager.OnPageChangeListener { override fun onPageScrollStateChanged(state: Int) {} @@ -47,11 +49,11 @@ class AnimatedOnboardingActivity : FragmentActivityBase(), OnNextClickedListener reportToAmplitude(AmplitudeAnalytic.Onboarding.SCREEN_OPENED) } } - onboardingViewPager.addOnPageChangeListener(pageChangeListener) - onboardingViewPager.setPageTransformer(false, OnboardingPageTransformer()) + onboardingBinding.onboardingViewPager.addOnPageChangeListener(pageChangeListener) + onboardingBinding.onboardingViewPager.setPageTransformer(false, OnboardingPageTransformer()) //we should post animation on next frame - onboardingViewPager.post { pageChangeListener.onPageSelected(onboardingViewPager.currentItem) } + onboardingBinding.onboardingViewPager.post { pageChangeListener.onPageSelected(onboardingBinding.onboardingViewPager.currentItem) } } private fun invokeAnimationOnFragment(position: Int) { @@ -61,8 +63,8 @@ class AnimatedOnboardingActivity : FragmentActivityBase(), OnNextClickedListener } private fun initClose() { - closeOnboarding.bringToFront() - closeOnboarding.setOnClickListener { + onboardingBinding.closeOnboarding.bringToFront() + onboardingBinding.closeOnboarding.setOnClickListener { onboardingClosed() } } @@ -73,10 +75,10 @@ class AnimatedOnboardingActivity : FragmentActivityBase(), OnNextClickedListener } override fun onNextClicked() { - val current = onboardingViewPager.currentItem + val current = onboardingBinding.onboardingViewPager.currentItem val next = current + 1 - if (next < onboardingViewPager.adapter!!.count) { - onboardingViewPager.setCurrentItem(next, true) + if (next < onboardingBinding.onboardingViewPager.adapter!!.count) { + onboardingBinding.onboardingViewPager.setCurrentItem(next, true) } else { onboardingComplete() } @@ -84,7 +86,7 @@ class AnimatedOnboardingActivity : FragmentActivityBase(), OnNextClickedListener private fun reportToAmplitude(eventName: String) { - val analyticPosition = onboardingViewPager.currentItem + 1 + val analyticPosition = onboardingBinding.onboardingViewPager.currentItem + 1 analytic.reportAmplitudeEvent(eventName, mapOf(AmplitudeAnalytic.Onboarding.PARAM_SCREEN to analyticPosition)) } @@ -112,11 +114,11 @@ class AnimatedOnboardingActivity : FragmentActivityBase(), OnNextClickedListener } } - private fun isFirstItem() = onboardingViewPager.currentItem == 0 + private fun isFirstItem() = onboardingBinding.onboardingViewPager.currentItem == 0 private fun showPreviousSlide() { - val previous = onboardingViewPager.currentItem - 1 - onboardingViewPager.setCurrentItem(previous, true) + val previous = onboardingBinding.onboardingViewPager.currentItem - 1 + onboardingBinding.onboardingViewPager.setCurrentItem(previous, true) } override fun applyTransitionPrev() { diff --git a/app/src/main/java/org/stepic/droid/ui/activities/PhotoViewActivity.kt b/app/src/main/java/org/stepic/droid/ui/activities/PhotoViewActivity.kt index d3c00edbf9..d9a7487c8e 100644 --- a/app/src/main/java/org/stepic/droid/ui/activities/PhotoViewActivity.kt +++ b/app/src/main/java/org/stepic/droid/ui/activities/PhotoViewActivity.kt @@ -6,12 +6,13 @@ import android.graphics.drawable.PictureDrawable import android.os.Bundle import android.view.MenuItem import androidx.core.view.isVisible +import by.kirich1409.viewbindingdelegate.viewBinding import com.bumptech.glide.request.target.CustomTarget import com.bumptech.glide.request.transition.Transition import com.github.chrisbanes.photoview.PhotoViewAttacher -import kotlinx.android.synthetic.main.fragment_photo_view.* import org.stepic.droid.R import org.stepic.droid.base.FragmentActivityBase +import org.stepic.droid.databinding.FragmentPhotoViewBinding import org.stepik.android.view.glide.model.GlideRequestFactory import kotlin.math.abs import kotlin.math.min @@ -21,14 +22,15 @@ class PhotoViewActivity : FragmentActivityBase() { const val EXTRA_PATH = "extra_path" } + private val photoViewBinding: FragmentPhotoViewBinding by viewBinding(FragmentPhotoViewBinding::bind) private lateinit var photoViewAttacher: PhotoViewAttacher private var screenHeight: Int = 0 private var dismissPathLength: Int = 0 private val target = object : CustomTarget() { override fun onResourceReady(resource: PictureDrawable, transition: Transition?) { - internetProblemRootView.isVisible = false - zoomableImageView.setImageDrawable(resource) + photoViewBinding.internetProblemRootView.isVisible = false + photoViewBinding.zoomableImageView.setImageDrawable(resource) photoViewAttacher.update() // Timber.d("resource ready") } @@ -38,7 +40,7 @@ class PhotoViewActivity : FragmentActivityBase() { } override fun onLoadFailed(errorDrawable: Drawable?) { - internetProblemRootView.isVisible = true + photoViewBinding.internetProblemRootView.isVisible = true } } @@ -46,34 +48,34 @@ class PhotoViewActivity : FragmentActivityBase() { super.onCreate(savedInstanceState) setContentView(R.layout.fragment_photo_view) setUpToolbar() - photoViewAttacher = PhotoViewAttacher(zoomableImageView) + photoViewAttacher = PhotoViewAttacher(photoViewBinding.zoomableImageView) screenHeight = Resources.getSystem().displayMetrics.heightPixels dismissPathLength = resources.getDimensionPixelSize(R.dimen.dismiss_path_length) - verticalDragLayout.setOnDragListener { dy -> + photoViewBinding.verticalDragLayout.setOnDragListener { dy -> if (photoViewAttacher.scale > 1f) return@setOnDragListener val alpha = 1 - min(abs(dy / (3 * dismissPathLength)), 1f) - backgroundColorView.alpha = alpha - toolbar.alpha = alpha - zoomableImageView.translationY = -dy + photoViewBinding.backgroundColorView.alpha = alpha + photoViewBinding.toolbar.alpha = alpha + photoViewBinding.zoomableImageView.translationY = -dy } - verticalDragLayout.setOnReleaseDragListener { dy -> + photoViewBinding.verticalDragLayout.setOnReleaseDragListener { dy -> if (photoViewAttacher.scale > 1f) return@setOnReleaseDragListener if (abs(dy) > dismissPathLength) { - zoomableImageView.isVisible = false + photoViewBinding.zoomableImageView.isVisible = false finish() } else { - backgroundColorView.alpha = 1f - toolbar.alpha = 1f - zoomableImageView.translationY = 0f + photoViewBinding.backgroundColorView.alpha = 1f + photoViewBinding.toolbar.alpha = 1f + photoViewBinding.zoomableImageView.translationY = 0f } } val url = requireNotNull(intent.getStringExtra(EXTRA_PATH)) - retryButton.setOnClickListener { - internetProblemRootView.isVisible = false + photoViewBinding.retryButton.setOnClickListener { + photoViewBinding.internetProblemRootView.isVisible = false loadImage(url) } loadImage(url) @@ -103,7 +105,7 @@ class PhotoViewActivity : FragmentActivityBase() { } private fun setUpToolbar() { - setSupportActionBar(toolbar) + setSupportActionBar(photoViewBinding.toolbar) val supportActionBar = supportActionBar if (supportActionBar != null) { diff --git a/app/src/main/java/org/stepic/droid/ui/adapters/SearchQueriesAdapter.kt b/app/src/main/java/org/stepic/droid/ui/adapters/SearchQueriesAdapter.kt index 207cc4ffe0..3128f3be5c 100644 --- a/app/src/main/java/org/stepic/droid/ui/adapters/SearchQueriesAdapter.kt +++ b/app/src/main/java/org/stepic/droid/ui/adapters/SearchQueriesAdapter.kt @@ -10,10 +10,10 @@ import android.widget.ImageView import android.widget.TextView import androidx.appcompat.widget.SearchView import androidx.recyclerview.widget.RecyclerView -import kotlinx.android.synthetic.main.search_query_item.view.* import org.stepic.droid.R import org.stepic.droid.analytic.Analytic import org.stepic.droid.base.App +import org.stepic.droid.databinding.SearchQueryItemBinding import org.stepic.droid.model.SearchQuery import org.stepic.droid.model.SearchQuerySource import org.stepic.droid.ui.custom.AutoCompleteSearchView @@ -58,8 +58,8 @@ class SearchQueriesAdapter(context: Context) : RecyclerView.Adapter userPreferences.isVibrateNotificationEnabled = isChecked } + notificationSettingsBinding.fragmentSettingsNotificationVibrationSwitch.isChecked = userPreferences.isVibrateNotificationEnabled + notificationSettingsBinding.fragmentSettingsNotificationVibrationSwitch.setOnCheckedChangeListener { _, isChecked -> userPreferences.isVibrateNotificationEnabled = isChecked } } private fun setUpSound() { - fragmentSettingsNotificationSoundSwitch.isChecked = userPreferences.isSoundNotificationEnabled - fragmentSettingsNotificationSoundSwitch.setOnCheckedChangeListener { _, isChecked -> userPreferences.setNotificationSoundEnabled(isChecked) } + notificationSettingsBinding.fragmentSettingsNotificationSoundSwitch.isChecked = userPreferences.isSoundNotificationEnabled + notificationSettingsBinding.fragmentSettingsNotificationSoundSwitch.setOnCheckedChangeListener { _, isChecked -> userPreferences.setNotificationSoundEnabled(isChecked) } } private fun setUpNotifications() { - fragmentSettingsNotificationLearnSwitch.isChecked = userPreferences.isNotificationEnabled(NotificationType.learn) - fragmentSettingsNotificationLearnSwitch.setOnCheckedChangeListener { _, isChecked -> userPreferences.setNotificationEnabled(NotificationType.learn, isChecked) } + notificationSettingsBinding.fragmentSettingsNotificationLearnSwitch.isChecked = userPreferences.isNotificationEnabled(NotificationType.learn) + notificationSettingsBinding.fragmentSettingsNotificationLearnSwitch.setOnCheckedChangeListener { _, isChecked -> userPreferences.setNotificationEnabled(NotificationType.learn, isChecked) } - fragmentSettingsNotificationCommentSwitch.isChecked = userPreferences.isNotificationEnabled(NotificationType.comments) - fragmentSettingsNotificationCommentSwitch.setOnCheckedChangeListener { _, isChecked -> userPreferences.setNotificationEnabled(NotificationType.comments, isChecked) } + notificationSettingsBinding.fragmentSettingsNotificationCommentSwitch.isChecked = userPreferences.isNotificationEnabled(NotificationType.comments) + notificationSettingsBinding.fragmentSettingsNotificationCommentSwitch.setOnCheckedChangeListener { _, isChecked -> userPreferences.setNotificationEnabled(NotificationType.comments, isChecked) } - fragmentSettingsNotificationReviewSwitch.isChecked = userPreferences.isNotificationEnabled(NotificationType.review) - fragmentSettingsNotificationReviewSwitch.setOnCheckedChangeListener { _, isChecked -> userPreferences.setNotificationEnabled(NotificationType.review, isChecked) } + notificationSettingsBinding.fragmentSettingsNotificationReviewSwitch.isChecked = userPreferences.isNotificationEnabled(NotificationType.review) + notificationSettingsBinding.fragmentSettingsNotificationReviewSwitch.setOnCheckedChangeListener { _, isChecked -> userPreferences.setNotificationEnabled(NotificationType.review, isChecked) } - fragmentSettingsNotificationTeachingSwitch.isChecked = userPreferences.isNotificationEnabled(NotificationType.teach) - fragmentSettingsNotificationTeachingSwitch.setOnCheckedChangeListener { _, isChecked -> userPreferences.setNotificationEnabled(NotificationType.teach, isChecked) } + notificationSettingsBinding.fragmentSettingsNotificationTeachingSwitch.isChecked = userPreferences.isNotificationEnabled(NotificationType.teach) + notificationSettingsBinding.fragmentSettingsNotificationTeachingSwitch.setOnCheckedChangeListener { _, isChecked -> userPreferences.setNotificationEnabled(NotificationType.teach, isChecked) } - fragmentSettingsNotificationOtherSwitch.isChecked = userPreferences.isNotificationEnabled(NotificationType.other) - fragmentSettingsNotificationOtherSwitch.setOnCheckedChangeListener { _, isChecked -> userPreferences.setNotificationEnabled(NotificationType.other, isChecked) } + notificationSettingsBinding.fragmentSettingsNotificationOtherSwitch.isChecked = userPreferences.isNotificationEnabled(NotificationType.other) + notificationSettingsBinding.fragmentSettingsNotificationOtherSwitch.setOnCheckedChangeListener { _, isChecked -> userPreferences.setNotificationEnabled(NotificationType.other, isChecked) } } diff --git a/app/src/main/java/org/stepic/droid/ui/fragments/OnboardingFragment.kt b/app/src/main/java/org/stepic/droid/ui/fragments/OnboardingFragment.kt index 0ae3cd8367..3665c17e02 100644 --- a/app/src/main/java/org/stepic/droid/ui/fragments/OnboardingFragment.kt +++ b/app/src/main/java/org/stepic/droid/ui/fragments/OnboardingFragment.kt @@ -4,14 +4,17 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import kotlinx.android.synthetic.main.fragment_onboarding_page.* +import by.kirich1409.viewbindingdelegate.viewBinding import org.stepic.droid.R import org.stepic.droid.base.FragmentBase +import org.stepic.droid.databinding.FragmentOnboardingPageBinding import org.stepic.droid.model.OnboardingType import org.stepic.droid.ui.activities.contracts.OnNextClickedListener import ru.nobird.android.view.base.ui.extension.argument class OnboardingFragment : FragmentBase() { + private val onboardingPageBinding: FragmentOnboardingPageBinding by viewBinding(FragmentOnboardingPageBinding::bind) + companion object { fun newInstance(onboardingType: OnboardingType): OnboardingFragment = OnboardingFragment() @@ -31,29 +34,29 @@ class OnboardingFragment : FragmentBase() { } private fun initScreen(type: OnboardingType) { - onboardingPageTitle.setText(type.title) - onboardingPageSubtitle.setText(type.subtitle) - onboardingPageAction.setText(type.getActionText()) + onboardingPageBinding.onboardingPageTitle.setText(type.title) + onboardingPageBinding.onboardingPageSubtitle.setText(type.subtitle) + onboardingPageBinding.onboardingPageAction.setText(type.getActionText()) - onboardingPageAction.setOnClickListener { + onboardingPageBinding.onboardingPageAction.setOnClickListener { (context as OnNextClickedListener).onNextClicked() } initAnimation(type) } private fun initAnimation(type: OnboardingType) { - onboardingAnimationView.visibility = View.VISIBLE - onboardingAnimationView.pauseAnimation() - onboardingAnimationView.setAnimation(type.assetPathToAnimation) + onboardingPageBinding.onboardingAnimationView.visibility = View.VISIBLE + onboardingPageBinding.onboardingAnimationView.pauseAnimation() + onboardingPageBinding.onboardingAnimationView.setAnimation(type.assetPathToAnimation) } fun startAnimation() { - onboardingAnimationView.setAnimation(onboardingType.assetPathToAnimation) - onboardingAnimationView.playAnimation() + onboardingPageBinding.onboardingAnimationView.setAnimation(onboardingType.assetPathToAnimation) + onboardingPageBinding.onboardingAnimationView.playAnimation() } override fun onPause() { super.onPause() - onboardingAnimationView.pauseAnimation() + onboardingPageBinding.onboardingAnimationView.pauseAnimation() } } diff --git a/app/src/main/java/org/stepic/droid/ui/fragments/StoreManagementFragment.kt b/app/src/main/java/org/stepic/droid/ui/fragments/StoreManagementFragment.kt index d52ebf19cf..cfa4ab4ed7 100644 --- a/app/src/main/java/org/stepic/droid/ui/fragments/StoreManagementFragment.kt +++ b/app/src/main/java/org/stepic/droid/ui/fragments/StoreManagementFragment.kt @@ -7,12 +7,13 @@ import android.view.View import androidx.core.view.isVisible import androidx.fragment.app.DialogFragment import androidx.fragment.app.Fragment -import kotlinx.android.synthetic.main.fragment_space_management.* +import by.kirich1409.viewbindingdelegate.viewBinding import org.stepic.droid.R import org.stepic.droid.analytic.Analytic import org.stepic.droid.base.App import org.stepic.droid.core.presenters.StoreManagementPresenter import org.stepic.droid.core.presenters.contracts.StoreManagementView +import org.stepic.droid.databinding.FragmentSpaceManagementBinding import org.stepic.droid.persistence.model.StorageLocation import org.stepic.droid.ui.dialogs.ChooseStorageDialog import org.stepic.droid.ui.dialogs.ClearVideosDialog @@ -32,6 +33,8 @@ class StoreManagementFragment : Fragment(R.layout.fragment_space_management), St StoreManagementFragment() } + private val storeManagementBinding: FragmentSpaceManagementBinding by viewBinding(FragmentSpaceManagementBinding::bind) + private var mClearCacheDialogFragment: DialogFragment? = null private var loadingProgressDialogFragment: DialogFragment? = null @@ -70,42 +73,42 @@ class StoreManagementFragment : Fragment(R.layout.fragment_space_management), St } override fun onDestroyView() { - clearCacheButton.setOnClickListener(null) - chooseStorageButton.setOnClickListener(null) + storeManagementBinding.clearCacheButton.setOnClickListener(null) + storeManagementBinding.chooseStorageButton.setOnClickListener(null) super.onDestroyView() } private fun hideAllStorageInfo() { - notMountExplanation.visibility = View.GONE - mountExplanation.visibility = View.GONE - chooseStorageButton.visibility = View.GONE + storeManagementBinding.notMountExplanation.visibility = View.GONE + storeManagementBinding.mountExplanation.visibility = View.GONE + storeManagementBinding.chooseStorageButton.visibility = View.GONE } override fun setStorageOptions(options: List, selectedOption: StorageLocation?) { when { options.size > 1 -> { - notMountExplanation.visibility = View.GONE - mountExplanation.visibility = View.VISIBLE - chooseStorageButton.visibility = View.VISIBLE + storeManagementBinding.notMountExplanation.visibility = View.GONE + storeManagementBinding.mountExplanation.visibility = View.VISIBLE + storeManagementBinding.chooseStorageButton.visibility = View.VISIBLE val chooseStorageDialog = ChooseStorageDialog.newInstance() chooseStorageDialog.setTargetFragment(this, 0) - chooseStorageButton.setOnClickListener { + storeManagementBinding.chooseStorageButton.setOnClickListener { if (!chooseStorageDialog.isAdded) { chooseStorageDialog.show(requireFragmentManager(), null) } } - userStorageInfo.isVisible = selectedOption != null + storeManagementBinding.userStorageInfo.isVisible = selectedOption != null if (selectedOption != null) { - userStorageInfo.text = + storeManagementBinding.userStorageInfo.text = storageLocationDescriptionMapper.mapToDescription(options.indexOf(selectedOption), selectedOption) } } options.size == 1 -> { - notMountExplanation.visibility = View.VISIBLE - mountExplanation.visibility = View.GONE - chooseStorageButton.visibility = View.GONE + storeManagementBinding.notMountExplanation.visibility = View.VISIBLE + storeManagementBinding.mountExplanation.visibility = View.GONE + storeManagementBinding.chooseStorageButton.visibility = View.GONE } else -> @@ -117,23 +120,23 @@ class StoreManagementFragment : Fragment(R.layout.fragment_space_management), St mClearCacheDialogFragment = ClearVideosDialog.newInstance() mClearCacheDialogFragment?.setTargetFragment(this, ClearVideosDialog.REQUEST_CODE) - clearCacheButton.setOnClickListener { + storeManagementBinding.clearCacheButton.setOnClickListener { analytic.reportEvent(Analytic.Interaction.CLICK_CLEAR_CACHE) if (mClearCacheDialogFragment?.isAdded != true) { mClearCacheDialogFragment?.show(requireFragmentManager(), ClearVideosDialog.TAG) } } - clearCacheButton.isEnabled = false + storeManagementBinding.clearCacheButton.isEnabled = false } override fun setUpClearCacheButton(cacheSize: Long) { if (cacheSize > 0) { - clearCacheButton.isEnabled = true - clearCacheLabel.text = TextUtil.formatBytes(cacheSize) + storeManagementBinding.clearCacheButton.isEnabled = true + storeManagementBinding.clearCacheLabel.text = TextUtil.formatBytes(cacheSize) } else { - clearCacheButton.isEnabled = false - clearCacheLabel.setText(R.string.empty) + storeManagementBinding.clearCacheButton.isEnabled = false + storeManagementBinding.clearCacheLabel.setText(R.string.empty) } } @@ -164,4 +167,4 @@ class StoreManagementFragment : Fragment(R.layout.fragment_space_management), St super.onActivityResult(requestCode, resultCode, data) } } -} \ No newline at end of file +} diff --git a/app/src/main/res/layout/activity_main_feed.xml b/app/src/main/res/layout/activity_main_feed.xml index 4a6d544eb8..d3f64c0743 100644 --- a/app/src/main/res/layout/activity_main_feed.xml +++ b/app/src/main/res/layout/activity_main_feed.xml @@ -2,10 +2,8 @@ + android:layout_height="match_parent"> + android:layout_height="match_parent"> android:orientation="vertical" - tools:viewBindingIgnore="true"> + android:padding="8dp"> + android:orientation="vertical"> + android:scrollbarStyle="outsideOverlay"> + android:layout_height="match_parent"> + android:scrollbars="none"> + android:background="@drawable/background_home_streak_view"> + android:layout_margin="12dp"> - +> +android:layout_gravity="center"> Date: Tue, 2 Jun 2026 21:16:28 +0200 Subject: [PATCH 08/65] refactor: migrate HomeFragment from synthetic to viewBinding - 3 synthetic imports (fragment_home, home_streak_view, view_centered_toolbar) - Added android:id to view_centered_toolbar include in view_centered_appbar Co-Authored-By: Claude Opus 4.8 --- .../stepic/droid/ui/fragments/HomeFragment.kt | 33 ++++++++++--------- .../main/res/layout/view_centered_appbar.xml | 4 ++- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/org/stepic/droid/ui/fragments/HomeFragment.kt b/app/src/main/java/org/stepic/droid/ui/fragments/HomeFragment.kt index c600b0ef54..37e0dad638 100644 --- a/app/src/main/java/org/stepic/droid/ui/fragments/HomeFragment.kt +++ b/app/src/main/java/org/stepic/droid/ui/fragments/HomeFragment.kt @@ -8,10 +8,8 @@ import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.fragment.app.Fragment import androidx.fragment.app.findFragment +import by.kirich1409.viewbindingdelegate.viewBinding import com.google.firebase.remoteconfig.FirebaseRemoteConfig -import kotlinx.android.synthetic.main.fragment_home.* -import kotlinx.android.synthetic.main.home_streak_view.* -import kotlinx.android.synthetic.main.view_centered_toolbar.* import org.stepic.droid.R import org.stepic.droid.analytic.AmplitudeAnalytic import org.stepic.droid.base.App @@ -19,6 +17,7 @@ import org.stepic.droid.base.FragmentBase import org.stepic.droid.configuration.RemoteConfig import org.stepic.droid.core.presenters.HomeStreakPresenter import org.stepic.droid.core.presenters.contracts.HomeStreakView +import org.stepic.droid.databinding.FragmentHomeBinding import org.stepic.droid.databinding.ItemBannerBinding import org.stepic.droid.util.commitNow import org.stepik.android.domain.banner.analytic.PromoBannerClickedAnalyticEvent @@ -50,6 +49,8 @@ class HomeFragment : FragmentBase(), HomeStreakView, FastContinueNewHomeFragment private const val fastContinueTag = "fastContinueTag" } + private val homeBinding: FragmentHomeBinding by viewBinding(FragmentHomeBinding::bind) + @Inject lateinit var homeStreakPresenter: HomeStreakPresenter @@ -83,18 +84,18 @@ class HomeFragment : FragmentBase(), HomeStreakView, FastContinueNewHomeFragment override fun onViewCreated(view: View, savedInstanceState: Bundle?) { nullifyActivityBackground() super.onViewCreated(view, savedInstanceState) - centeredToolbarTitle.setText(R.string.home_title) + homeBinding.appBarLayout.centeredToolbarContainer.centeredToolbarTitle.setText(R.string.home_title) if (savedInstanceState == null) { setupFragments(remoteConfig.getBoolean(RemoteConfig.IS_NEW_HOME_SCREEN_ENABLED)) } - appBarLayout.isVisible = !remoteConfig.getBoolean(RemoteConfig.IS_NEW_HOME_SCREEN_ENABLED) + homeBinding.appBarLayout.root.isVisible = !remoteConfig.getBoolean(RemoteConfig.IS_NEW_HOME_SCREEN_ENABLED) homeStreakPresenter.attachView(this) homeStreakPresenter.onNeedShowStreak() - homeMainContainer.post { setupBanners() } + homeBinding.homeMainContainer.post { setupBanners() } } override fun onStart() { @@ -103,7 +104,7 @@ class HomeFragment : FragmentBase(), HomeStreakView, FastContinueNewHomeFragment SharedTransitionsManager.registerTransitionDelegate(HOME_DEEPLINK_STORY_KEY, object : SharedTransitionContainerDelegate { override fun getSharedView(position: Int): View? = - storyDeepLinkMockView + homeBinding.storyDeepLinkMockView override fun onPositionChanged(position: Int) {} }) @@ -123,17 +124,17 @@ class HomeFragment : FragmentBase(), HomeStreakView, FastContinueNewHomeFragment } override fun showStreak(streak: Int) { - streakCounter.text = streak.toString() + homeBinding.homeStreak.streakCounter.text = streak.toString() val daysPlural = resources.getQuantityString(R.plurals.day_number, streak) val days = "$streak $daysPlural" - streakText.text = textResolver.fromHtml(getString(R.string.home_streak_counter_text, days)) - homeStreak.isVisible = true + homeBinding.homeStreak.streakText.text = textResolver.fromHtml(getString(R.string.home_streak_counter_text, days)) + homeBinding.homeStreak.root.isVisible = true } override fun onEmptyStreak() { - homeStreak.isVisible = false + homeBinding.homeStreak.root.isVisible = false } private fun setupFragments(isNewHomeScreenEnabled: Boolean) { @@ -174,7 +175,7 @@ class HomeFragment : FragmentBase(), HomeStreakView, FastContinueNewHomeFragment } banners.forEach { banner -> - val binding = ItemBannerBinding.inflate(layoutInflater, homeMainContainer, false) + val binding = ItemBannerBinding.inflate(layoutInflater, homeBinding.homeMainContainer, false) binding.root.setOnClickListener { // TODO // Probably better to move into ViewTreeObserver @@ -186,10 +187,10 @@ class HomeFragment : FragmentBase(), HomeStreakView, FastContinueNewHomeFragment binding.bind(banner, bannerResourcesMapper) - val insertionIndex = min(banner.position + offset, homeMainContainer.childCount) - val previousFragment = homeMainContainer.getChildAt(insertionIndex - 1).findFragment() + val insertionIndex = min(banner.position + offset, homeBinding.homeMainContainer.childCount) + val previousFragment = homeBinding.homeMainContainer.getChildAt(insertionIndex - 1).findFragment() - homeMainContainer.addView(binding.root, insertionIndex) + homeBinding.homeMainContainer.addView(binding.root, insertionIndex) binding.root.updateLayoutParams { val margin = if (previousFragment is LearningActionsFragment) { @@ -208,6 +209,6 @@ class HomeFragment : FragmentBase(), HomeStreakView, FastContinueNewHomeFragment } else { 0 } - homeNestedScrollView.setPadding(0, 0, 0, padding) + homeBinding.homeNestedScrollView.setPadding(0, 0, 0, padding) } } diff --git a/app/src/main/res/layout/view_centered_appbar.xml b/app/src/main/res/layout/view_centered_appbar.xml index ccbd6f9684..bd9057ec2f 100644 --- a/app/src/main/res/layout/view_centered_appbar.xml +++ b/app/src/main/res/layout/view_centered_appbar.xml @@ -5,6 +5,8 @@ android:layout_height="wrap_content" android:elevation="?appBarElevation"> - + \ No newline at end of file From 4a0731a6137f465a967eb669354c04e4eaac41c2 Mon Sep 17 00:00:00 2001 From: Aleksandr Zhukov Date: Tue, 2 Jun 2026 21:22:01 +0200 Subject: [PATCH 09/65] refactor: migrate MainFeedActivity from synthetic to viewBinding - 2 view references (navigationView, frame) Co-Authored-By: Claude Opus 4.8 --- .../droid/ui/activities/MainFeedActivity.kt | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/org/stepic/droid/ui/activities/MainFeedActivity.kt b/app/src/main/java/org/stepic/droid/ui/activities/MainFeedActivity.kt index e9fff74f19..d1467b03b7 100644 --- a/app/src/main/java/org/stepic/droid/ui/activities/MainFeedActivity.kt +++ b/app/src/main/java/org/stepic/droid/ui/activities/MainFeedActivity.kt @@ -14,9 +14,10 @@ import androidx.annotation.IdRes import androidx.annotation.RequiresApi import androidx.core.app.ActivityCompat import androidx.fragment.app.Fragment +import by.kirich1409.viewbindingdelegate.viewBinding import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.firebase.remoteconfig.FirebaseRemoteConfig -import kotlinx.android.synthetic.main.activity_main_feed.* +import org.stepic.droid.databinding.ActivityMainFeedBinding import org.stepic.droid.BuildConfig import org.stepic.droid.R import org.stepic.droid.analytic.Analytic @@ -70,6 +71,8 @@ class MainFeedActivity : BackToExitActivityWithSmartLockBase(), StreakNotificationDialogFragment.Callback, TimeIntervalPickerDialogFragment.Companion.Callback { + private val mainFeedBinding: ActivityMainFeedBinding by viewBinding(ActivityMainFeedBinding::bind) + companion object { const val CURRENT_INDEX_KEY = "currentIndexKey" @@ -236,7 +239,7 @@ class MainFeedActivity : BackToExitActivityWithSmartLockBase(), when (getFragmentIndexFromIntent(launchIntent)) { HOME_INDEX -> { - navigationView.selectedItemId = R.id.home + mainFeedBinding.navigationView.selectedItemId = R.id.home val storyId = launchIntent?.getStoryId() if (storyId != null) { StoryDeepLinkDialogFragment @@ -245,7 +248,7 @@ class MainFeedActivity : BackToExitActivityWithSmartLockBase(), } } CATALOG_INDEX -> { - navigationView.selectedItemId = R.id.catalog + mainFeedBinding.navigationView.selectedItemId = R.id.catalog launchIntent?.data?.pathSegments?.let { when { it.contains(CATALOG_DEEPLINK) -> { @@ -268,9 +271,9 @@ class MainFeedActivity : BackToExitActivityWithSmartLockBase(), } } } - PROFILE_INDEX -> navigationView.selectedItemId = R.id.profile - NOTIFICATIONS_INDEX -> navigationView.selectedItemId = R.id.notifications - DEBUG_INDEX -> navigationView.selectedItemId = R.id.debug + PROFILE_INDEX -> mainFeedBinding.navigationView.selectedItemId = R.id.profile + NOTIFICATIONS_INDEX -> mainFeedBinding.navigationView.selectedItemId = R.id.notifications + DEBUG_INDEX -> mainFeedBinding.navigationView.selectedItemId = R.id.debug else -> { //do nothing } @@ -299,9 +302,9 @@ class MainFeedActivity : BackToExitActivityWithSmartLockBase(), } private fun initNavigation() { - navigationView.setOnNavigationItemSelectedListener(::onNavigationItemSelected) - navigationView.setOnNavigationItemReselectedListener(::onNavigationItemReselected) - navigationView.menu.findItem(R.id.debug).isVisible = BuildConfig.BUILD_TYPE == DEBUG_BUILD_TYPE || BuildConfig.BUILD_TYPE == STAGE_DEBUGGABLE_BUILD_TYPE + mainFeedBinding.navigationView.setOnNavigationItemSelectedListener(::onNavigationItemSelected) + mainFeedBinding.navigationView.setOnNavigationItemReselectedListener(::onNavigationItemReselected) + mainFeedBinding.navigationView.menu.findItem(R.id.debug).isVisible = BuildConfig.BUILD_TYPE == DEBUG_BUILD_TYPE || BuildConfig.BUILD_TYPE == STAGE_DEBUGGABLE_BUILD_TYPE } private fun showCurrentFragment(@IdRes id: Int) { @@ -319,11 +322,11 @@ class MainFeedActivity : BackToExitActivityWithSmartLockBase(), } override fun onBackPressed() { - if (navigationView.selectedItemId == R.id.home) { + if (mainFeedBinding.navigationView.selectedItemId == R.id.home) { finish() return } else { - navigationView.selectedItemId = R.id.home + mainFeedBinding.navigationView.selectedItemId = R.id.home } } @@ -413,8 +416,8 @@ class MainFeedActivity : BackToExitActivityWithSmartLockBase(), //RootScreen methods override fun showCatalog() { - if (navigationView.selectedItemId != R.id.catalog) { - navigationView.selectedItemId = R.id.catalog + if (mainFeedBinding.navigationView.selectedItemId != R.id.catalog) { + mainFeedBinding.navigationView.selectedItemId = R.id.catalog } } @@ -445,11 +448,11 @@ class MainFeedActivity : BackToExitActivityWithSmartLockBase(), } override fun onBadgeShouldBeHidden() { - navigationView.removeBadge(R.id.notifications) + mainFeedBinding.navigationView.removeBadge(R.id.notifications) } override fun onBadgeCountChanged(count: Int) { - val badge = navigationView.getOrCreateBadge(R.id.notifications) + val badge = mainFeedBinding.navigationView.getOrCreateBadge(R.id.notifications) badge.number = count badge.maxCharacterCount = 3 badge.isVisible = true @@ -479,7 +482,7 @@ class MainFeedActivity : BackToExitActivityWithSmartLockBase(), if (deniedPermissionIndex != -1) { if (!ActivityCompat.shouldShowRequestPermissionRationale(this, permissions[deniedPermissionIndex])) { - frame.snackbar(messageRes = R.string.notification_permission_error) + mainFeedBinding.frame.snackbar(messageRes = R.string.notification_permission_error) } } else { TimeIntervalPickerDialogFragment From 8b414569033a49d10f594e1e2d3203b4d0b8b332 Mon Sep 17 00:00:00 2001 From: Aleksandr Zhukov Date: Tue, 2 Jun 2026 23:36:34 +0200 Subject: [PATCH 10/65] refactor: migrate 5 files from synthetic to viewBinding - ChoiceQuizDelegate.kt - PopupHelper.kt - AchievementTileDelegate.kt - AchievementDetailsDialog.kt - AchievementsListFragment.kt Co-Authored-By: Claude Opus 4.8 --- .../droid/ui/quiz/ChoiceQuizDelegate.kt | 4 +- .../org/stepic/droid/ui/util/PopupHelper.kt | 11 ++-- .../ui/delegate/AchievementTileDelegate.kt | 10 ++-- .../ui/dialog/AchievementDetailsDialog.kt | 54 +++++++++---------- .../ui/fragment/AchievementsListFragment.kt | 26 ++++----- .../dialog_achievement_details.xml | 3 +- .../error_no_connection_with_button.xml | 3 +- .../res/layout/dialog_achievement_details.xml | 3 +- .../res/layout/fragment_achievements_list.xml | 8 +-- app/src/main/res/layout/popup_window.xml | 4 +- .../main/res/layout/view_choice_attempt.xml | 3 +- 11 files changed, 63 insertions(+), 66 deletions(-) diff --git a/app/src/main/java/org/stepic/droid/ui/quiz/ChoiceQuizDelegate.kt b/app/src/main/java/org/stepic/droid/ui/quiz/ChoiceQuizDelegate.kt index 678bc60d54..6fd14d5fb9 100644 --- a/app/src/main/java/org/stepic/droid/ui/quiz/ChoiceQuizDelegate.kt +++ b/app/src/main/java/org/stepic/droid/ui/quiz/ChoiceQuizDelegate.kt @@ -3,9 +3,9 @@ package org.stepic.droid.ui.quiz import android.view.View import android.view.ViewGroup import android.widget.Button -import kotlinx.android.synthetic.main.view_choice_attempt.view.* import org.stepic.droid.R import org.stepic.droid.ui.adapters.StepikRadioGroupAdapter +import org.stepic.droid.databinding.ViewChoiceAttemptBinding import org.stepik.android.model.Submission import org.stepik.android.model.attempts.Attempt import ru.nobird.android.view.base.ui.extension.inflate @@ -26,7 +26,7 @@ class ChoiceQuizDelegate: QuizDelegate() { parent.inflate(R.layout.view_choice_attempt, false) override fun onViewCreated(view: View) { - choiceAdapter = StepikRadioGroupAdapter(view.choice_container) + choiceAdapter = StepikRadioGroupAdapter(ViewChoiceAttemptBinding.bind(view).choiceContainer) } override fun setSubmission(submission: Submission?) = choiceAdapter.setSubmission(submission) diff --git a/app/src/main/java/org/stepic/droid/ui/util/PopupHelper.kt b/app/src/main/java/org/stepic/droid/ui/util/PopupHelper.kt index b6df8cbfe4..8762357b2d 100644 --- a/app/src/main/java/org/stepic/droid/ui/util/PopupHelper.kt +++ b/app/src/main/java/org/stepic/droid/ui/util/PopupHelper.kt @@ -10,8 +10,8 @@ import android.widget.PopupWindow import androidx.annotation.DrawableRes import androidx.core.view.isVisible import androidx.core.widget.PopupWindowCompat -import kotlinx.android.synthetic.main.popup_window.view.* import org.stepic.droid.R +import org.stepic.droid.databinding.PopupWindowBinding object PopupHelper { enum class PopupTheme( @@ -44,10 +44,11 @@ object PopupHelper { anchorView ?: return null val inflater = context.getSystemService(LAYOUT_INFLATER_SERVICE) as LayoutInflater - val popupView = inflater.inflate(R.layout.popup_window, null) + val binding = PopupWindowBinding.inflate(inflater) + val popupView = binding.root - val popupTextView = popupView.popupText - val popupArrowView = popupView.arrowView + val popupTextView = binding.popupText + val popupArrowView = binding.arrowView popupTextView.text = popupText popupTextView.setBackgroundResource(theme.backgroundRes) @@ -57,7 +58,7 @@ object PopupHelper { if (withArrow) { popupView.viewTreeObserver.addOnGlobalLayoutListener { - popupArrowView.x = calcArrowHorizontalOffset(anchorView, popupView, popupView.arrowView) + popupArrowView.x = calcArrowHorizontalOffset(anchorView, popupView, popupArrowView) } } diff --git a/app/src/main/java/org/stepik/android/view/achievement/ui/delegate/AchievementTileDelegate.kt b/app/src/main/java/org/stepik/android/view/achievement/ui/delegate/AchievementTileDelegate.kt index e4e227c04e..86b4f88435 100644 --- a/app/src/main/java/org/stepik/android/view/achievement/ui/delegate/AchievementTileDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/achievement/ui/delegate/AchievementTileDelegate.kt @@ -3,8 +3,8 @@ package org.stepik.android.view.achievement.ui.delegate import android.view.View import androidx.appcompat.content.res.AppCompatResources import androidx.core.view.isGone -import kotlinx.android.synthetic.main.view_achievement_tile.view.* import org.stepic.droid.R +import org.stepic.droid.databinding.ViewAchievementTileBinding import org.stepik.android.view.achievement.ui.resolver.AchievementResourceResolver import org.stepik.android.domain.achievement.model.AchievementItem import org.stepik.android.view.glide.ui.extension.wrapWithGlide @@ -15,9 +15,11 @@ class AchievementTileDelegate( root: View, private val achievementResourceResolver: AchievementResourceResolver ) { - private val achievementLevels: VectorRatingBar = root.achievementLevels - private val achievementLevelProgress: AchievementCircleProgressView = root.achievementLevelProgress - private val achievementIcon = root.achievementIcon.wrapWithGlide() + private val binding = ViewAchievementTileBinding.bind(root) + + private val achievementLevels: VectorRatingBar = binding.achievementLevels + private val achievementLevelProgress: AchievementCircleProgressView = binding.achievementLevelProgress + private val achievementIcon = binding.achievementIcon.wrapWithGlide() private val achievementIconSize = root.resources.getDimensionPixelSize(R.dimen.achievement_tile_width) private val achievementIconPlaceholder = diff --git a/app/src/main/java/org/stepik/android/view/achievement/ui/dialog/AchievementDetailsDialog.kt b/app/src/main/java/org/stepik/android/view/achievement/ui/dialog/AchievementDetailsDialog.kt index 0cfc1f0ac0..ecddb9d028 100644 --- a/app/src/main/java/org/stepik/android/view/achievement/ui/dialog/AchievementDetailsDialog.kt +++ b/app/src/main/java/org/stepik/android/view/achievement/ui/dialog/AchievementDetailsDialog.kt @@ -8,11 +8,11 @@ import androidx.appcompat.content.res.AppCompatResources import androidx.core.view.isVisible import androidx.fragment.app.DialogFragment import com.google.android.material.dialog.MaterialAlertDialogBuilder -import kotlinx.android.synthetic.main.dialog_achievement_details.view.* import org.stepic.droid.R import org.stepic.droid.analytic.AmplitudeAnalytic import org.stepic.droid.analytic.Analytic import org.stepic.droid.base.App +import org.stepic.droid.databinding.DialogAchievementDetailsBinding import org.stepik.android.view.glide.ui.extension.wrapWithGlide import org.stepik.android.domain.achievement.model.AchievementItem import org.stepik.android.view.achievement.ui.resolver.AchievementResourceResolver @@ -51,36 +51,34 @@ class AchievementDetailsDialog : DialogFragment() { } override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val view = LayoutInflater.from(context).inflate(R.layout.dialog_achievement_details, null, false) - - view.apply { - achievementTitle.text = achievementResourceResolver.resolveTitleForKind(achievementItem.kind) - achievementDescription.text = achievementResourceResolver.resolveDescription(achievementItem) - achievementIcon.apply { - wrapWithGlide() - .setImagePath( - achievementResourceResolver.resolveAchievementIcon(achievementItem, resources.getDimensionPixelSize(R.dimen.achievement_details_icon_size)), - placeholder = AppCompatResources.getDrawable(context, R.drawable.ic_achievement_empty) - ) - } - - achievementLevelProgress.progress = achievementItem.currentScore.toFloat() / achievementItem.targetScore - achievementLevels.progress = achievementItem.currentLevel - achievementLevels.total = achievementItem.maxLevel - - achievementLevel.text = getString(R.string.achievement_level, achievementItem.currentLevel, achievementItem.maxLevel) - - val scoreDiff = achievementItem.targetScore - achievementItem.currentScore - achievementRest.text = if (achievementItem.isLocked) { - getString(R.string.achievement_remaining_exp_locked) - } else { - getString(R.string.achievement_remaining_exp, scoreDiff) - } - achievementRest.isVisible = scoreDiff > 0 + val binding = DialogAchievementDetailsBinding.inflate(LayoutInflater.from(context)) + + binding.achievementTitle.text = achievementResourceResolver.resolveTitleForKind(achievementItem.kind) + binding.achievementDescription.text = achievementResourceResolver.resolveDescription(achievementItem) + binding.achievementIcon.apply { + wrapWithGlide() + .setImagePath( + achievementResourceResolver.resolveAchievementIcon(achievementItem, resources.getDimensionPixelSize(R.dimen.achievement_details_icon_size)), + placeholder = AppCompatResources.getDrawable(context, R.drawable.ic_achievement_empty) + ) } + binding.achievementLevelProgress.progress = achievementItem.currentScore.toFloat() / achievementItem.targetScore + binding.achievementLevels.progress = achievementItem.currentLevel + binding.achievementLevels.total = achievementItem.maxLevel + + binding.achievementLevel.text = getString(R.string.achievement_level, achievementItem.currentLevel, achievementItem.maxLevel) + + val scoreDiff = achievementItem.targetScore - achievementItem.currentScore + binding.achievementRest.text = if (achievementItem.isLocked) { + getString(R.string.achievement_remaining_exp_locked) + } else { + getString(R.string.achievement_remaining_exp, scoreDiff) + } + binding.achievementRest.isVisible = scoreDiff > 0 + val builder = MaterialAlertDialogBuilder(requireContext()) - .setView(view) + .setView(binding.root) if (canShare && !achievementItem.isLocked) { builder diff --git a/app/src/main/java/org/stepik/android/view/achievement/ui/fragment/AchievementsListFragment.kt b/app/src/main/java/org/stepik/android/view/achievement/ui/fragment/AchievementsListFragment.kt index b0c85fbc76..bb9f536c6e 100644 --- a/app/src/main/java/org/stepik/android/view/achievement/ui/fragment/AchievementsListFragment.kt +++ b/app/src/main/java/org/stepik/android/view/achievement/ui/fragment/AchievementsListFragment.kt @@ -10,12 +10,12 @@ import androidx.fragment.app.viewModels import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager -import kotlinx.android.synthetic.main.error_no_connection_with_button.* -import kotlinx.android.synthetic.main.fragment_achievements_list.* +import by.kirich1409.viewbindingdelegate.viewBinding import org.stepic.droid.R import org.stepic.droid.analytic.AmplitudeAnalytic import org.stepic.droid.analytic.Analytic import org.stepic.droid.base.App +import org.stepic.droid.databinding.FragmentAchievementsListBinding import org.stepic.droid.ui.util.initCenteredToolbar import org.stepik.android.domain.achievement.model.AchievementItem import org.stepik.android.presentation.achievement.AchievementsPresenter @@ -30,6 +30,8 @@ import ru.nobird.android.view.base.ui.extension.showIfNotExists import javax.inject.Inject class AchievementsListFragment : Fragment(), AchievementsView { + private val achievementsListBinding: FragmentAchievementsListBinding by viewBinding(FragmentAchievementsListBinding::bind) + companion object { fun newInstance(userId: Long, isMyProfile: Boolean): Fragment = AchievementsListFragment().apply { @@ -78,26 +80,26 @@ class AchievementsListFragment : Fragment(), AchievementsView { val context = requireContext() viewStateDelegate = ViewStateDelegate() - viewStateDelegate.addState(progress) - viewStateDelegate.addState(progress) - viewStateDelegate.addState(error) - viewStateDelegate.addState(recycler) + viewStateDelegate.addState(achievementsListBinding.progress) + viewStateDelegate.addState(achievementsListBinding.progress) + viewStateDelegate.addState(achievementsListBinding.errorLayout.root) + viewStateDelegate.addState(achievementsListBinding.recycler) initPlaceholders() initCenteredToolbar(R.string.achievements_title, showHomeButton = true) - recycler.layoutManager = LinearLayoutManager(context) - recycler.adapter = achievementsAdapter + achievementsListBinding.recycler.layoutManager = LinearLayoutManager(context) + achievementsListBinding.recycler.adapter = achievementsAdapter val divider = DividerItemDecoration(context, DividerItemDecoration.VERTICAL) divider.setDrawable(ContextCompat.getDrawable(context, R.drawable.bg_divider_vertical)!!) - recycler.addItemDecoration(divider) + achievementsListBinding.recycler.addItemDecoration(divider) achievementsPresenter.attachView(this) fetchAchievements() - tryAgain.setOnClickListener { fetchAchievements(true) } + achievementsListBinding.errorLayout.tryAgain.setOnClickListener { fetchAchievements(true) } } private fun initPlaceholders() { @@ -105,8 +107,8 @@ class AchievementsListFragment : Fragment(), AchievementsView { val screenHeight = resources.displayMetrics.heightPixels for (i in 0..(screenHeight / itemHeight).toInt()) { - progress.addView(layoutInflater.inflate(R.layout.view_achievement_item_placeholder, progress, false)) - progress.addView(layoutInflater.inflate(R.layout.view_divider_vertical, progress, false)) + achievementsListBinding.progress.addView(layoutInflater.inflate(R.layout.view_achievement_item_placeholder, achievementsListBinding.progress, false)) + achievementsListBinding.progress.addView(layoutInflater.inflate(R.layout.view_divider_vertical, achievementsListBinding.progress, false)) } } diff --git a/app/src/main/res/layout-land/dialog_achievement_details.xml b/app/src/main/res/layout-land/dialog_achievement_details.xml index b0e7aba4a1..d0a628da09 100644 --- a/app/src/main/res/layout-land/dialog_achievement_details.xml +++ b/app/src/main/res/layout-land/dialog_achievement_details.xml @@ -4,8 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="wrap_content" - android:layout_height="wrap_content" - tools:viewBindingIgnore="true"> + android:layout_height="wrap_content"> + tools:visibility="visible"> + android:layout_height="wrap_content"> + android:layout_height="match_parent"> - + + android:layout_height="wrap_content"> \ No newline at end of file + tools:layout_height="300dp" /> \ No newline at end of file From f8135ec4e9727d6b57c0d975daaed3dedc062dcf Mon Sep 17 00:00:00 2001 From: Aleksandr Zhukov Date: Tue, 2 Jun 2026 23:46:15 +0200 Subject: [PATCH 11/65] refactor: migrate 5 files from synthetic to viewBinding - CredentialAuthActivity.kt - RegistrationActivity.kt - SocialAuthActivity.kt - ItemBannerBindingExtension.kt - AuthorAdapterDelegate.kt Co-Authored-By: Claude Opus 4.8 --- .../ui/activity/CredentialAuthActivity.kt | 69 +++++++-------- .../auth/ui/activity/RegistrationActivity.kt | 83 ++++++++++--------- .../auth/ui/activity/SocialAuthActivity.kt | 47 ++++++----- .../extension/ItemBannerBindingExtension.kt | 3 +- .../adapter/delegate/AuthorAdapterDelegate.kt | 22 +++-- .../layout_author_properties.xml | 3 +- .../res/layout/activity_auth_credential.xml | 1 - .../main/res/layout/activity_auth_social.xml | 1 - .../main/res/layout/activity_registration.xml | 1 - app/src/main/res/layout/item_author.xml | 3 +- .../res/layout/layout_author_properties.xml | 3 +- 11 files changed, 118 insertions(+), 118 deletions(-) diff --git a/app/src/main/java/org/stepik/android/view/auth/ui/activity/CredentialAuthActivity.kt b/app/src/main/java/org/stepik/android/view/auth/ui/activity/CredentialAuthActivity.kt index c5bc30dfb1..c75af86365 100644 --- a/app/src/main/java/org/stepik/android/view/auth/ui/activity/CredentialAuthActivity.kt +++ b/app/src/main/java/org/stepik/android/view/auth/ui/activity/CredentialAuthActivity.kt @@ -15,11 +15,12 @@ import androidx.core.content.res.ResourcesCompat import androidx.core.view.isVisible import androidx.fragment.app.DialogFragment import androidx.lifecycle.ViewModelProvider -import kotlinx.android.synthetic.main.activity_auth_credential.* +import by.kirich1409.viewbindingdelegate.viewBinding import org.stepic.droid.R import org.stepic.droid.analytic.Analytic import org.stepic.droid.analytic.LoginInteractionType import org.stepic.droid.base.App +import org.stepic.droid.databinding.ActivityAuthCredentialBinding import org.stepic.droid.model.Credentials import org.stepic.droid.ui.activities.SmartLockActivityBase import org.stepic.droid.ui.dialogs.LoadingProgressDialogFragment @@ -37,6 +38,8 @@ import ru.nobird.android.view.base.ui.extension.hideKeyboard import javax.inject.Inject class CredentialAuthActivity : SmartLockActivityBase(), CredentialAuthView { + private val binding: ActivityAuthCredentialBinding by viewBinding(ActivityAuthCredentialBinding::bind) + companion object { private const val EXTRA_EMAIL = "extra_email" private const val EXTRA_PASSWORD = "extra_password" @@ -79,20 +82,20 @@ class CredentialAuthActivity : SmartLockActivityBase(), CredentialAuthView { initTitle() - forgotPasswordView.setOnClickListener { + binding.forgotPasswordView.setOnClickListener { screenManager.openRemindPassword(this@CredentialAuthActivity) } - loginField.setOnEditorActionListener { _, actionId, _ -> + binding.loginField.setOnEditorActionListener { _, actionId, _ -> var handled = false if (actionId == EditorInfo.IME_ACTION_NEXT) { - passwordField.requestFocus() + binding.passwordField.requestFocus() handled = true } handled } - passwordField.setOnEditorActionListener { _, actionId, _ -> + binding.passwordField.setOnEditorActionListener { _, actionId, _ -> var handled = false if (actionId == EditorInfo.IME_ACTION_SEND) { analytic.reportEvent(Analytic.Interaction.CLICK_SIGN_IN_NEXT_ON_SIGN_IN_SCREEN) @@ -108,8 +111,8 @@ class CredentialAuthActivity : SmartLockActivityBase(), CredentialAuthView { analytic.reportEvent(Analytic.Login.TAP_ON_FIELDS) } } - loginField.setOnFocusChangeListener(onFocusField) - passwordField.setOnFocusChangeListener(onFocusField) + binding.loginField.setOnFocusChangeListener(onFocusField) + binding.passwordField.setOnFocusChangeListener(onFocusField) val reportAnalyticWhenTextBecomeNotBlank = object : TextWatcher { override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { @@ -124,10 +127,10 @@ class CredentialAuthActivity : SmartLockActivityBase(), CredentialAuthView { override fun afterTextChanged(s: Editable?) {} } - loginField.addTextChangedListener(reportAnalyticWhenTextBecomeNotBlank) - passwordField.addTextChangedListener(reportAnalyticWhenTextBecomeNotBlank) + binding.loginField.addTextChangedListener(reportAnalyticWhenTextBecomeNotBlank) + binding.passwordField.addTextChangedListener(reportAnalyticWhenTextBecomeNotBlank) - launchSignUpButton.setOnClickListener { + binding.launchSignUpButton.setOnClickListener { analytic.reportEvent(Analytic.Interaction.CLICK_SIGN_UP) screenManager .showRegistration( @@ -136,22 +139,22 @@ class CredentialAuthActivity : SmartLockActivityBase(), CredentialAuthView { ) } - signInWithSocial.setOnClickListener { finish() } - loginButton.setOnClickListener { + binding.signInWithSocial.setOnClickListener { finish() } + binding.loginButton.setOnClickListener { analytic.reportEvent(Analytic.Interaction.CLICK_SIGN_IN_ON_SIGN_IN_SCREEN) analytic.reportEvent(Analytic.Login.REQUEST_LOGIN_WITH_INTERACTION_TYPE, LoginInteractionType.button.toBundle()) submit() } - loginRootView.requestFocus() + binding.loginRootView.requestFocus() initGoogleApiClient() - setOnKeyboardOpenListener(root_view, { - stepikLogo.isVisible = false - signInText.isVisible = false + setOnKeyboardOpenListener(binding.rootView, { + binding.stepikLogo.isVisible = false + binding.signInText.isVisible = false }, { - stepikLogo.isVisible = true - signInText.isVisible = true + binding.stepikLogo.isVisible = true + binding.signInText.isVisible = true }) if (savedInstanceState == null) { @@ -185,7 +188,7 @@ class CredentialAuthActivity : SmartLockActivityBase(), CredentialAuthView { spannableSignIn.setSpan(TypefaceSpanCompat(typeface), 0, signInString.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) - signInText.text = spannableSignIn + binding.signInText.text = spannableSignIn } override fun onNewIntent(intent: Intent) { @@ -201,15 +204,15 @@ class CredentialAuthActivity : SmartLockActivityBase(), CredentialAuthView { val email = intent.getStringExtra(EXTRA_EMAIL) val password = intent.getStringExtra(EXTRA_PASSWORD) - loginField.setText(email) - passwordField.setText(password) + binding.loginField.setText(email) + binding.passwordField.setText(password) when { email == null -> - loginField.requestFocus() + binding.loginField.requestFocus() - passwordField == null -> - passwordField.requestFocus() + password == null -> + binding.passwordField.requestFocus() } if (autoAuth != AutoAuth.NONE) { @@ -220,8 +223,8 @@ class CredentialAuthActivity : SmartLockActivityBase(), CredentialAuthView { private fun submit(autoAuth: AutoAuth = AutoAuth.NONE) { currentFocus?.hideKeyboard() - val login = loginField.text.toString() - val password = passwordField.text.toString() + val login = binding.loginField.text.toString() + val password = binding.passwordField.text.toString() credentialAuthPresenter.submit(Credentials(login, password), isRegistration = autoAuth == AutoAuth.REGISTRATION) } @@ -237,19 +240,19 @@ class CredentialAuthActivity : SmartLockActivityBase(), CredentialAuthView { when (state) { is CredentialAuthView.State.Idle -> { - loginButton.isEnabled = true - loginForm.isEnabled = true - loginErrorMessage.isVisible = false + binding.loginButton.isEnabled = true + binding.loginForm.isEnabled = true + binding.loginErrorMessage.isVisible = false } is CredentialAuthView.State.Error -> { - loginErrorMessage.text = getMessageFor(state.failType) - loginErrorMessage.isVisible = true + binding.loginErrorMessage.text = getMessageFor(state.failType) + binding.loginErrorMessage.isVisible = true if (state.failType == LoginFailType.EMAIL_ALREADY_USED || state.failType == LoginFailType.EMAIL_PASSWORD_INVALID) { - loginForm.isEnabled = false - loginButton.isEnabled = false + binding.loginForm.isEnabled = false + binding.loginButton.isEnabled = false } } diff --git a/app/src/main/java/org/stepik/android/view/auth/ui/activity/RegistrationActivity.kt b/app/src/main/java/org/stepik/android/view/auth/ui/activity/RegistrationActivity.kt index e5772829b5..d86fb45f44 100644 --- a/app/src/main/java/org/stepik/android/view/auth/ui/activity/RegistrationActivity.kt +++ b/app/src/main/java/org/stepik/android/view/auth/ui/activity/RegistrationActivity.kt @@ -16,11 +16,12 @@ import androidx.core.content.res.ResourcesCompat import androidx.core.view.isVisible import androidx.fragment.app.DialogFragment import androidx.lifecycle.ViewModelProvider -import kotlinx.android.synthetic.main.activity_registration.* +import by.kirich1409.viewbindingdelegate.viewBinding import org.stepic.droid.R import org.stepic.droid.analytic.Analytic import org.stepic.droid.analytic.LoginInteractionType import org.stepic.droid.base.App +import org.stepic.droid.databinding.ActivityRegistrationBinding import org.stepic.droid.ui.activities.SmartLockActivityBase import org.stepic.droid.ui.dialogs.LoadingProgressDialogFragment import org.stepic.droid.ui.util.setOnKeyboardOpenListener @@ -39,6 +40,8 @@ import ru.nobird.android.view.base.ui.extension.hideKeyboard import javax.inject.Inject class RegistrationActivity : SmartLockActivityBase(), RegistrationView { + private val binding: ActivityRegistrationBinding by viewBinding(ActivityRegistrationBinding::bind) + companion object { private const val ERROR_DELIMITER = " " @@ -76,13 +79,13 @@ class RegistrationActivity : SmartLockActivityBase(), RegistrationView { initTitle() - termsPrivacyRegisterTextView.movementMethod = LinkMovementMethod.getInstance() - termsPrivacyRegisterTextView.text = textResolver.fromHtml(termsMessageHtml) - stripUnderlinesFromLinks(termsPrivacyRegisterTextView) + binding.termsPrivacyRegisterTextView.movementMethod = LinkMovementMethod.getInstance() + binding.termsPrivacyRegisterTextView.text = textResolver.fromHtml(termsMessageHtml) + stripUnderlinesFromLinks(binding.termsPrivacyRegisterTextView) - signUpButton.setOnClickListener { submit(LoginInteractionType.button) } + binding.signUpButton.setOnClickListener { submit(LoginInteractionType.button) } - passwordField.setOnEditorActionListener { _, actionId, _ -> + binding.passwordField.setOnEditorActionListener { _, actionId, _ -> var handled = false if (actionId == EditorInfo.IME_ACTION_SEND) { analytic.reportEvent(Analytic.Registration.CLICK_SEND_IME) @@ -107,49 +110,49 @@ class RegistrationActivity : SmartLockActivityBase(), RegistrationView { } } - firstNameField.addTextChangedListener(reportAnalyticWhenTextBecomeNotBlank) - emailField.addTextChangedListener(reportAnalyticWhenTextBecomeNotBlank) - passwordField.addTextChangedListener(reportAnalyticWhenTextBecomeNotBlank) + binding.firstNameField.addTextChangedListener(reportAnalyticWhenTextBecomeNotBlank) + binding.emailField.addTextChangedListener(reportAnalyticWhenTextBecomeNotBlank) + binding.passwordField.addTextChangedListener(reportAnalyticWhenTextBecomeNotBlank) val onFocusChangeListener = View.OnFocusChangeListener { _, hasFocus -> if (hasFocus) { analytic.reportEvent(Analytic.Registration.TAP_ON_FIELDS) } } - firstNameField.onFocusChangeListener = onFocusChangeListener - emailField.onFocusChangeListener = onFocusChangeListener - passwordField.onFocusChangeListener = onFocusChangeListener + binding.firstNameField.onFocusChangeListener = onFocusChangeListener + binding.emailField.onFocusChangeListener = onFocusChangeListener + binding.passwordField.onFocusChangeListener = onFocusChangeListener - firstNameField.setOnEditorActionListener { _, actionId, _ -> + binding.firstNameField.setOnEditorActionListener { _, actionId, _ -> if (actionId == EditorInfo.IME_ACTION_NEXT) { - emailField.requestFocus() + binding.emailField.requestFocus() true } else { false } } - emailField.setOnEditorActionListener { _, actionId, _ -> + binding.emailField.setOnEditorActionListener { _, actionId, _ -> if (actionId == EditorInfo.IME_ACTION_NEXT) { - passwordField.requestFocus() + binding.passwordField.requestFocus() true } else { false } } - registerRootView.requestFocus() + binding.registerRootView.requestFocus() initGoogleApiClient() setSignUpButtonState() - setOnKeyboardOpenListener(root_view, { - stepikLogo.isVisible = false - signUpText.isVisible = false + setOnKeyboardOpenListener(binding.rootView, { + binding.stepikLogo.isVisible = false + binding.signUpText.isVisible = false }, { - stepikLogo.isVisible = true - signUpText.isVisible = true + binding.stepikLogo.isVisible = true + binding.signUpText.isVisible = true }) } @@ -171,10 +174,10 @@ class RegistrationActivity : SmartLockActivityBase(), RegistrationView { } private fun setSignUpButtonState() { - signUpButton.isEnabled = - emailField.text.isNullOrBlank() == false && - firstNameField.text.isNullOrBlank() == false && - passwordField.text.isNullOrBlank() == false + binding.signUpButton.isEnabled = + binding.emailField.text.isNullOrBlank() == false && + binding.firstNameField.text.isNullOrBlank() == false && + binding.passwordField.text.isNullOrBlank() == false } private fun initTitle() { @@ -186,17 +189,17 @@ class RegistrationActivity : SmartLockActivityBase(), RegistrationView { spannableSignIn.setSpan(TypefaceSpanCompat(typeface), 0, signUpString.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) - signUpText.text = spannableSignIn + binding.signUpText.text = spannableSignIn } private fun submit(interactionType: LoginInteractionType) { analytic.reportEvent(Analytic.Registration.CLICK_WITH_INTERACTION_TYPE, interactionType.toBundle()) currentFocus?.hideKeyboard() - val firstName = firstNameField.text.toString().trim() + val firstName = binding.firstNameField.text.toString().trim() val lastName = " " // registrationSecondName.text.toString().trim() - val email = emailField.text.toString().trim() - val password = passwordField.text.toString() + val email = binding.emailField.text.toString().trim() + val password = binding.passwordField.text.toString() analytic.reportEvent(Analytic.Interaction.CLICK_REGISTER_BUTTON) @@ -221,9 +224,9 @@ class RegistrationActivity : SmartLockActivityBase(), RegistrationView { when (state) { is RegistrationView.State.Idle -> { - signUpButton.isEnabled = true - registerForm.isEnabled = true - registerErrorMessage.isVisible = false + binding.signUpButton.isEnabled = true + binding.registerForm.isEnabled = true + binding.registerErrorMessage.isVisible = false } is RegistrationView.State.Error -> { @@ -249,7 +252,7 @@ class RegistrationActivity : SmartLockActivityBase(), RegistrationView { } override fun showNetworkError() { - registerRootView.snackbar(messageRes = R.string.connectionProblems) + binding.registerRootView.snackbar(messageRes = R.string.connectionProblems) } override fun applyTransitionPrev() {} // we need default system animation @@ -257,11 +260,11 @@ class RegistrationActivity : SmartLockActivityBase(), RegistrationView { private fun showError(errorText: String?) { errorText?.let { analytic.reportEventWithName(Analytic.Registration.ERROR, errorText) - if (registerErrorMessage.visibility == View.GONE) { - signUpButton.isEnabled = false - registerForm.isEnabled = false - registerErrorMessage.text = it - registerErrorMessage.isVisible = true + if (binding.registerErrorMessage.visibility == View.GONE) { + binding.signUpButton.isEnabled = false + binding.registerForm.isEnabled = false + binding.registerErrorMessage.text = it + binding.registerErrorMessage.isVisible = true } } } @@ -270,4 +273,4 @@ class RegistrationActivity : SmartLockActivityBase(), RegistrationView { values ?.takeIf { it.isNotEmpty() } ?.joinToString(separator = ERROR_DELIMITER) -} \ No newline at end of file +} diff --git a/app/src/main/java/org/stepik/android/view/auth/ui/activity/SocialAuthActivity.kt b/app/src/main/java/org/stepik/android/view/auth/ui/activity/SocialAuthActivity.kt index 11a7097314..b7df6df5ff 100644 --- a/app/src/main/java/org/stepik/android/view/auth/ui/activity/SocialAuthActivity.kt +++ b/app/src/main/java/org/stepik/android/view/auth/ui/activity/SocialAuthActivity.kt @@ -12,6 +12,7 @@ import androidx.core.view.isVisible import androidx.fragment.app.DialogFragment import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.GridLayoutManager +import by.kirich1409.viewbindingdelegate.viewBinding import com.google.android.gms.auth.api.Auth import com.google.android.gms.common.api.GoogleApiClient import com.vk.api.sdk.VK @@ -20,12 +21,12 @@ import com.vk.api.sdk.auth.VKAuthCallback import com.vk.api.sdk.auth.VKScope import com.vk.api.sdk.exceptions.VKApiCodes import jp.wasabeef.recyclerview.animators.FadeInDownAnimator -import kotlinx.android.synthetic.main.activity_auth_social.* import org.stepic.droid.R import org.stepic.droid.analytic.Analytic import org.stepic.droid.analytic.experiments.DeferredAuthSplitTest import org.stepic.droid.analytic.experiments.OnboardingSplitTestVersion2 import org.stepic.droid.base.App +import org.stepic.droid.databinding.ActivityAuthSocialBinding import org.stepic.droid.model.Credentials import org.stepic.droid.preferences.SharedPreferenceHelper import org.stepic.droid.ui.activities.MainFeedActivity @@ -45,6 +46,8 @@ import org.stepik.android.view.base.ui.span.TypefaceSpanCompat import javax.inject.Inject class SocialAuthActivity : SmartLockActivityBase(), SocialAuthView { + private val binding: ActivityAuthSocialBinding by viewBinding(ActivityAuthSocialBinding::bind) + companion object { private const val REQUEST_CODE_GOOGLE_SIGN_IN = 7007 @@ -101,19 +104,19 @@ class SocialAuthActivity : SmartLockActivityBase(), SocialAuthView { overridePendingTransition(R.anim.no_transition, R.anim.slide_out_to_bottom) - dismissButton.setOnClickListener { + binding.dismissButton.setOnClickListener { onBackPressed() } - dismissButton.isVisible = true -// dismissButton.isVisible = deferredAuthSplitTest.currentGroup.isDeferredAuth || onboardingSplitTest.currentGroup == OnboardingSplitTest.Group.Personalized + binding.dismissButton.isVisible = true +// binding.dismissButton.isVisible = deferredAuthSplitTest.currentGroup.isDeferredAuth || onboardingSplitTest.currentGroup == OnboardingSplitTest.Group.Personalized - launchSignUpButton.setOnClickListener { + binding.launchSignUpButton.setOnClickListener { analytic.reportEvent(Analytic.Interaction.CLICK_SIGN_UP) screenManager.showRegistration(this@SocialAuthActivity, course) } - signInWithEmail.setOnClickListener { + binding.signInWithEmail.setOnClickListener { analytic.reportEvent(Analytic.Interaction.CLICK_SIGN_IN) screenManager.showLogin(this@SocialAuthActivity, null, null, AutoAuth.NONE, course) } @@ -137,7 +140,7 @@ class SocialAuthActivity : SmartLockActivityBase(), SocialAuthView { spannableSignIn.setSpan(typefaceSpan, 0, signInString.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) - signInText.text = spannableSignIn + binding.signInText.text = spannableSignIn // callbackManager = CallbackManager.Factory.create() // LoginManager.getInstance().registerCallback(callbackManager, object : FacebookCallback { @@ -194,30 +197,30 @@ class SocialAuthActivity : SmartLockActivityBase(), SocialAuthView { } private fun initSocialRecycler(state: SocialAuthAdapter.State = SocialAuthAdapter.State.NORMAL) { - socialListRecyclerView.layoutManager = GridLayoutManager(this, 3) + binding.socialListRecyclerView.layoutManager = GridLayoutManager(this, 3) - socialListRecyclerView.itemAnimator = FadeInDownAnimator() + binding.socialListRecyclerView.itemAnimator = FadeInDownAnimator() .apply { removeDuration = 0 } val adapter = SocialAuthAdapter(this::onSocialItemClicked, state) - showMore.setOnClickListener { - showMore.isVisible = false - showLess.isVisible = true + binding.showMore.setOnClickListener { + binding.showMore.isVisible = false + binding.showLess.isVisible = true adapter.showMore() } - showLess.setOnClickListener { - showLess.isVisible = false - showMore.isVisible = true + binding.showLess.setOnClickListener { + binding.showLess.isVisible = false + binding.showMore.isVisible = true adapter.showLess() } - showLess.isVisible = state == SocialAuthAdapter.State.EXPANDED - showMore.isVisible = state == SocialAuthAdapter.State.NORMAL + binding.showLess.isVisible = state == SocialAuthAdapter.State.EXPANDED + binding.showMore.isVisible = state == SocialAuthAdapter.State.NORMAL - socialListRecyclerView.adapter = adapter + binding.socialListRecyclerView.adapter = adapter } private fun onSocialItemClicked(type: SocialNetwork) { @@ -226,7 +229,7 @@ class SocialAuthActivity : SmartLockActivityBase(), SocialAuthView { SocialNetwork.GOOGLE -> { if (googleApiClient == null) { analytic.reportEvent(Analytic.Interaction.GOOGLE_SOCIAL_IS_NOT_ENABLED) - root_view.snackbar(messageRes = R.string.google_services_late) + binding.rootView.snackbar(messageRes = R.string.google_services_late) } else { val signInIntent = Auth.GoogleSignInApi.getSignInIntent(googleApiClient) startActivityForResult(signInIntent, REQUEST_CODE_GOOGLE_SIGN_IN) @@ -348,7 +351,7 @@ class SocialAuthActivity : SmartLockActivityBase(), SocialAuthView { } override fun showAuthError(failType: LoginFailType) { - root_view.snackbar(message = getMessageFor(failType)) + binding.rootView.snackbar(message = getMessageFor(failType)) // logout from socials VK.logout() @@ -360,7 +363,7 @@ class SocialAuthActivity : SmartLockActivityBase(), SocialAuthView { } override fun showNetworkError() { - root_view.snackbar(messageRes = R.string.connectionProblems) + binding.rootView.snackbar(messageRes = R.string.connectionProblems) } override fun onSocialLoginWithExistingEmail(email: String) { @@ -368,7 +371,7 @@ class SocialAuthActivity : SmartLockActivityBase(), SocialAuthView { } override fun onSaveInstanceState(outState: Bundle) { - val adapter = socialListRecyclerView.adapter + val adapter = binding.socialListRecyclerView.adapter if (adapter is SocialAuthAdapter) { outState.putSerializable(KEY_SOCIAL_ADAPTER_STATE, adapter.state) } diff --git a/app/src/main/java/org/stepik/android/view/banner/extension/ItemBannerBindingExtension.kt b/app/src/main/java/org/stepik/android/view/banner/extension/ItemBannerBindingExtension.kt index 37b6e325e7..98e9c1c916 100644 --- a/app/src/main/java/org/stepik/android/view/banner/extension/ItemBannerBindingExtension.kt +++ b/app/src/main/java/org/stepik/android/view/banner/extension/ItemBannerBindingExtension.kt @@ -4,7 +4,6 @@ import android.net.Uri import androidx.appcompat.content.res.AppCompatResources import androidx.core.view.ViewCompat import androidx.fragment.app.FragmentManager -import kotlinx.android.synthetic.main.item_banner.view.* import org.stepic.droid.databinding.ItemBannerBinding import org.stepik.android.domain.banner.model.Banner import org.stepik.android.view.banner.mapper.BannerResourcesMapper @@ -29,6 +28,6 @@ fun ItemBannerBinding.bind(banner: Banner, bannerResourcesMapper: BannerResource val descriptionTextColorRes = bannerResourcesMapper.mapBannerTypeToDescriptionTextColor(root.context, banner.type) bannerImage.setImageResource(imageRes) - ViewCompat.setBackgroundTintList(root.bannerRoot, AppCompatResources.getColorStateList(root.context, backgroundColorRes)) + ViewCompat.setBackgroundTintList(bannerRoot, AppCompatResources.getColorStateList(root.context, backgroundColorRes)) bannerDescription.setTextColor(descriptionTextColorRes) } \ No newline at end of file diff --git a/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/AuthorAdapterDelegate.kt b/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/AuthorAdapterDelegate.kt index 3052543305..50b64acec7 100644 --- a/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/AuthorAdapterDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/AuthorAdapterDelegate.kt @@ -1,12 +1,10 @@ package org.stepik.android.view.catalog.ui.adapter.delegate -import android.view.View import android.view.ViewGroup +import by.kirich1409.viewbindingdelegate.viewBinding import com.bumptech.glide.Glide -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_author.* -import kotlinx.android.synthetic.main.layout_author_properties.view.* import org.stepic.droid.R +import org.stepic.droid.databinding.ItemAuthorBinding import org.stepic.droid.util.TextUtil import org.stepik.android.domain.catalog.model.CatalogAuthor import ru.nobird.android.ui.adapterdelegates.AdapterDelegate @@ -21,14 +19,14 @@ class AuthorAdapterDelegate( override fun onCreateViewHolder(parent: ViewGroup): DelegateViewHolder = ViewHolder(createView(parent, R.layout.item_author)) - private inner class ViewHolder( - override val containerView: View - ) : DelegateViewHolder(containerView), LayoutContainer { - private val authorCourseCount = authorListPropertiesContainer.coursesCountText - private val authorSubscriberCount = authorListPropertiesContainer.subscribersCountText + private inner class ViewHolder(root: android.view.View) : DelegateViewHolder(root) { + private val viewBinding: ItemAuthorBinding by viewBinding { ItemAuthorBinding.bind(root) } + + private val authorCourseCount = viewBinding.authorListPropertiesContainer.coursesCountText + private val authorSubscriberCount = viewBinding.authorListPropertiesContainer.subscribersCountText init { - containerView.setOnClickListener { itemData?.id?.let(onItemClick) } + root.setOnClickListener { itemData?.id?.let(onItemClick) } } override fun onBind(data: CatalogAuthor) { @@ -38,9 +36,9 @@ class AuthorAdapterDelegate( .load(data.avatar) .placeholder(R.drawable.general_placeholder) .fitCenter() - .into(authorListImage) + .into(viewBinding.authorListImage) - authorListTitle.text = data.fullName + viewBinding.authorListTitle.text = data.fullName authorCourseCount.text = context.resources.getQuantityString(R.plurals.course_count, data.createdCoursesCount, data.createdCoursesCount) authorSubscriberCount.text = context.resources.getString(R.string.author_subscribers, TextUtil.formatNumbers(data.followersCount.toLong())) } diff --git a/app/src/main/res/layout-w360dp/layout_author_properties.xml b/app/src/main/res/layout-w360dp/layout_author_properties.xml index e24dd3d289..5148cc5d4e 100644 --- a/app/src/main/res/layout-w360dp/layout_author_properties.xml +++ b/app/src/main/res/layout-w360dp/layout_author_properties.xml @@ -4,8 +4,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/tools" - tools:viewBindingIgnore="true"> + xmlns:tools="http://schemas.android.com/tools"> + app:cardCornerRadius="@dimen/course_item_radius"> + xmlns:tools="http://schemas.android.com/tools"> Date: Tue, 2 Jun 2026 23:58:14 +0200 Subject: [PATCH 12/65] refactor: migrate 5 files from synthetic to viewBinding - AuthorListAdapterDelegate.kt - CourseListAdapterDelegate.kt - FiltersAdapterDelegate.kt - OfflineAdapterDelegate.kt - RecommendedCourseListAdapterDelegate.kt Co-Authored-By: Claude Opus 4.8 --- .../delegate/AuthorListAdapterDelegate.kt | 25 ++++++++----------- .../delegate/CourseListAdapterDelegate.kt | 9 ++++--- .../delegate/FiltersAdapterDelegate.kt | 4 +-- .../delegate/OfflineAdapterDelegate.kt | 19 +++++++------- .../RecommendedCourseListAdapterDelegate.kt | 21 ++++++++-------- .../main/res/layout/header_catalog_block.xml | 3 +-- app/src/main/res/layout/item_author_list.xml | 8 +++--- .../main/res/layout/item_course_list_new.xml | 8 +++--- .../main/res/layout/view_course_languages.xml | 4 +-- 9 files changed, 49 insertions(+), 52 deletions(-) diff --git a/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/AuthorListAdapterDelegate.kt b/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/AuthorListAdapterDelegate.kt index e4bc219362..bbfcb8ca1c 100644 --- a/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/AuthorListAdapterDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/AuthorListAdapterDelegate.kt @@ -1,13 +1,11 @@ package org.stepik.android.view.catalog.ui.adapter.delegate -import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.LinearSnapHelper import androidx.recyclerview.widget.RecyclerView -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.header_catalog_block.* -import kotlinx.android.synthetic.main.item_author_list.* +import by.kirich1409.viewbindingdelegate.viewBinding import org.stepic.droid.R +import org.stepic.droid.databinding.ItemAuthorListBinding import org.stepik.android.domain.catalog.model.CatalogAuthor import org.stepik.android.presentation.course_list_redux.model.CatalogBlockStateWrapper import org.stepik.android.view.base.ui.adapter.layoutmanager.TableLayoutManager @@ -31,12 +29,11 @@ class AuthorListAdapterDelegate( override fun onCreateViewHolder(parent: ViewGroup): DelegateViewHolder = AuthorListViewHolder(createView(parent, R.layout.item_author_list)) - private inner class AuthorListViewHolder( - override val containerView: View - ) : DelegateViewHolder(containerView), LayoutContainer { + private inner class AuthorListViewHolder(root: android.view.View) : DelegateViewHolder(root) { + private val viewBinding: ItemAuthorListBinding by viewBinding { ItemAuthorListBinding.bind(root) } private val catalogBlockTitleDelegate = - CatalogBlockHeaderDelegate(catalogBlockContainer, null) + CatalogBlockHeaderDelegate(viewBinding.catalogBlockHeader.root, null) private val adapter = DefaultDelegateAdapter() .also { @@ -45,7 +42,7 @@ class AuthorListAdapterDelegate( init { val rowCount = context.resources.getInteger(R.integer.author_lists_default_rows) - authorListRecycler.layoutManager = + viewBinding.authorListRecycler.layoutManager = TableLayoutManager( context, horizontalSpanCount = context.resources.getInteger(R.integer.author_lists_default_columns), @@ -53,12 +50,12 @@ class AuthorListAdapterDelegate( orientation = RecyclerView.HORIZONTAL, reverseLayout = false ) - authorListRecycler.setRecycledViewPool(sharedViewPool) - authorListRecycler.setHasFixedSize(true) - authorListRecycler.adapter = adapter + viewBinding.authorListRecycler.setRecycledViewPool(sharedViewPool) + viewBinding.authorListRecycler.setHasFixedSize(true) + viewBinding.authorListRecycler.adapter = adapter val snapHelper = LinearSnapHelper() - snapHelper.attachToRecyclerView(authorListRecycler) + snapHelper.attachToRecyclerView(viewBinding.authorListRecycler) } override fun onBind(data: CatalogItem) { @@ -74,4 +71,4 @@ class AuthorListAdapterDelegate( catalogBlockTitleDelegate.setCount(authorCountMapper.mapAuthorCountToString(context, count)) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/CourseListAdapterDelegate.kt b/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/CourseListAdapterDelegate.kt index df6d630aac..47dac181df 100644 --- a/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/CourseListAdapterDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/CourseListAdapterDelegate.kt @@ -4,12 +4,12 @@ import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.LinearSnapHelper import androidx.recyclerview.widget.RecyclerView +import by.kirich1409.viewbindingdelegate.viewBinding import dagger.assisted.Assisted import dagger.assisted.AssistedInject -import kotlinx.android.synthetic.main.header_catalog_block.view.* -import kotlinx.android.synthetic.main.item_course_list_new.view.* import org.stepic.droid.R import org.stepic.droid.analytic.Analytic +import org.stepic.droid.databinding.ItemCourseListNewBinding import org.stepik.android.domain.catalog.model.CatalogBlock import org.stepik.android.domain.catalog.model.CatalogBlockContent import org.stepik.android.domain.course.analytic.CourseViewSource @@ -56,12 +56,13 @@ constructor( CourseCollectionViewHolder(createView(parent, R.layout.item_course_list_new)) private inner class CourseCollectionViewHolder(root: View) : DelegateViewHolder(root) { + private val viewBinding: ItemCourseListNewBinding by viewBinding { ItemCourseListNewBinding.bind(root) } private var catalogBlock: CatalogBlock? = null private var courseCount: Int? = null - private val courseListCoursesRecycler = root.courseListCoursesRecycler - private val courseListTitleContainer = root.catalogBlockContainer + private val courseListCoursesRecycler = viewBinding.courseListCoursesRecycler + private val courseListTitleContainer = viewBinding.catalogBlockHeader.root private val catalogBlockTitleDelegate = CatalogBlockHeaderDelegate(courseListTitleContainer) { val block = (catalogBlock?.content as? CatalogBlockContent.FullCourseList) ?: return@CatalogBlockHeaderDelegate diff --git a/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/FiltersAdapterDelegate.kt b/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/FiltersAdapterDelegate.kt index 788c69a284..956fb8b75b 100644 --- a/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/FiltersAdapterDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/FiltersAdapterDelegate.kt @@ -3,8 +3,8 @@ package org.stepik.android.view.catalog.ui.adapter.delegate import android.view.View import android.view.ViewGroup import com.google.android.material.button.MaterialButtonToggleGroup -import kotlinx.android.synthetic.main.view_course_languages.view.* import org.stepic.droid.R +import org.stepic.droid.databinding.ViewCourseLanguagesBinding import org.stepic.droid.model.StepikFilter import org.stepik.android.presentation.filter.FiltersFeature import org.stepik.android.view.catalog.model.CatalogItem @@ -24,7 +24,7 @@ class FiltersAdapterDelegate( private inner class FiltersViewHolder(root: View) : DelegateViewHolder(root) { private val viewStateDelegate = ViewStateDelegate() - private val languages = itemView.languages + private val languages = ViewCourseLanguagesBinding.bind(itemView).languages init { val toggleListener = diff --git a/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/OfflineAdapterDelegate.kt b/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/OfflineAdapterDelegate.kt index 7c5b82747f..94379d99e2 100644 --- a/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/OfflineAdapterDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/OfflineAdapterDelegate.kt @@ -5,9 +5,9 @@ import android.view.ViewGroup import androidx.core.view.doOnLayout import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.error_no_connection_with_button_small.* +import by.kirich1409.viewbindingdelegate.viewBinding import org.stepic.droid.R +import org.stepic.droid.databinding.ErrorNoConnectionWithButtonSmallBinding import org.stepik.android.view.catalog.model.CatalogItem import ru.nobird.app.core.model.safeCast import ru.nobird.android.ui.adapterdelegates.AdapterDelegate @@ -28,13 +28,14 @@ class OfflineAdapterDelegate( } private class OfflineViewHolder( - override val containerView: View, + root: View, private val onRetry: () -> Unit - ) : DelegateViewHolder(containerView), LayoutContainer { + ) : DelegateViewHolder(root) { + private val viewBinding: ErrorNoConnectionWithButtonSmallBinding by viewBinding { ErrorNoConnectionWithButtonSmallBinding.bind(root) } init { - tryAgain.setOnClickListener { onRetry() } - containerView.isVisible = true + viewBinding.tryAgain.setOnClickListener { onRetry() } + root.isVisible = true } override fun onBind(data: CatalogItem) { @@ -42,13 +43,13 @@ class OfflineAdapterDelegate( itemView.doOnLayout { val parent = it.parent.safeCast() ?: return@doOnLayout - val remainingHeight = parent.height - containerView.bottom - containerView.top + val remainingHeight = parent.height - itemView.bottom - itemView.top if (remainingHeight > 0) { itemView.updateLayoutParams { - height = containerView.height + remainingHeight + height = itemView.height + remainingHeight } } } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/RecommendedCourseListAdapterDelegate.kt b/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/RecommendedCourseListAdapterDelegate.kt index 8c98b933ed..82bbdaf878 100644 --- a/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/RecommendedCourseListAdapterDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/RecommendedCourseListAdapterDelegate.kt @@ -4,13 +4,12 @@ import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.LinearSnapHelper import androidx.recyclerview.widget.RecyclerView +import by.kirich1409.viewbindingdelegate.viewBinding import dagger.assisted.Assisted import dagger.assisted.AssistedInject -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.header_catalog_block.view.* -import kotlinx.android.synthetic.main.item_course_list_new.* import org.stepic.droid.R import org.stepic.droid.analytic.Analytic +import org.stepic.droid.databinding.ItemCourseListNewBinding import org.stepik.android.domain.catalog.model.CatalogBlock import org.stepik.android.domain.catalog.model.CatalogBlockContent import org.stepik.android.domain.course.analytic.CourseViewSource @@ -54,11 +53,13 @@ constructor( CourseRecommendationsViewHolder(createView(parent, R.layout.item_course_list_new)) private inner class CourseRecommendationsViewHolder( - override val containerView: View - ) : DelegateViewHolder(containerView), LayoutContainer { + root: View + ) : DelegateViewHolder(root) { + private val viewBinding: ItemCourseListNewBinding by viewBinding { ItemCourseListNewBinding.bind(root) } + private var catalogBlock: CatalogBlock? = null - private val courseListTitleContainer = containerView.catalogBlockContainer + private val courseListTitleContainer = viewBinding.catalogBlockHeader.root private val catalogBlockTitleDelegate = CatalogBlockHeaderDelegate(courseListTitleContainer) private val skeletonCount = context.resources.getInteger(R.integer.course_list_rows) * context.resources.getInteger(R.integer.course_list_columns) @@ -69,9 +70,9 @@ constructor( private var tableLayoutManager: TableLayoutManager init { - viewStateDelegate.addState(courseListCoursesRecycler) - viewStateDelegate.addState(courseListTitleContainer, courseListCoursesRecycler) - viewStateDelegate.addState(courseListTitleContainer, courseListCoursesRecycler) + viewStateDelegate.addState(viewBinding.courseListCoursesRecycler) + viewStateDelegate.addState(courseListTitleContainer, viewBinding.courseListCoursesRecycler) + viewStateDelegate.addState(courseListTitleContainer, viewBinding.courseListCoursesRecycler) viewStateDelegate.addState() viewStateDelegate.addState() @@ -90,7 +91,7 @@ constructor( val columnsCount = context.resources.getInteger(R.integer.course_list_columns) tableLayoutManager = TableLayoutManager(context, columnsCount, rowCount, RecyclerView.HORIZONTAL, false) - with(courseListCoursesRecycler) { + with(viewBinding.courseListCoursesRecycler) { adapter = courseItemAdapter layoutManager = tableLayoutManager itemAnimator?.changeDuration = 0 diff --git a/app/src/main/res/layout/header_catalog_block.xml b/app/src/main/res/layout/header_catalog_block.xml index 57c5bb23cc..1688d2ac16 100644 --- a/app/src/main/res/layout/header_catalog_block.xml +++ b/app/src/main/res/layout/header_catalog_block.xml @@ -8,8 +8,7 @@ android:paddingBottom="16dp" android:visibility="visible" android:layout_width="match_parent" - android:layout_height="wrap_content" - tools:viewBindingIgnore="true"> + android:layout_height="wrap_content"> + android:orientation="vertical"> - + + android:orientation="vertical"> - + + android:orientation="vertical"> From c2493d87b99cc2c57cba75af93ad81d843922237 Mon Sep 17 00:00:00 2001 From: Aleksandr Zhukov Date: Wed, 3 Jun 2026 00:06:39 +0200 Subject: [PATCH 13/65] refactor: migrate 5 files from synthetic to viewBinding - SimpleCourseListDefaultAdapterDelegate.kt - SimpleCourseListGridAdapterDelegate.kt - SimpleCourseListGridFirstAdapter.kt - SimpleCourseListsDefaultAdapterDelegate.kt - SimpleCourseListsGridAdapterDelegate.kt Co-Authored-By: Claude Opus 4.8 --- .../SimpleCourseListDefaultAdapterDelegate.kt | 23 ++++++++-------- .../SimpleCourseListGridAdapterDelegate.kt | 16 +++++------- .../SimpleCourseListGridFirstAdapter.kt | 19 +++++++------- ...SimpleCourseListsDefaultAdapterDelegate.kt | 26 +++++++++---------- .../SimpleCourseListsGridAdapterDelegate.kt | 24 ++++++++--------- .../item_block_simple_course_list_grid.xml | 3 +-- ...em_block_simple_course_list_grid_first.xml | 3 +-- ...item_block_simple_course_lists_default.xml | 7 ++--- .../item_block_simple_course_lists_grid.xml | 7 ++--- .../item_simple_course_list_default.xml | 3 +-- 10 files changed, 61 insertions(+), 70 deletions(-) diff --git a/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/SimpleCourseListDefaultAdapterDelegate.kt b/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/SimpleCourseListDefaultAdapterDelegate.kt index 641e751be8..7b28768099 100644 --- a/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/SimpleCourseListDefaultAdapterDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/SimpleCourseListDefaultAdapterDelegate.kt @@ -1,12 +1,11 @@ package org.stepik.android.view.catalog.ui.adapter.delegate -import android.view.View import android.view.ViewGroup import androidx.appcompat.content.res.AppCompatResources import androidx.core.view.ViewCompat -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_simple_course_list_default.* +import by.kirich1409.viewbindingdelegate.viewBinding import org.stepic.droid.R +import org.stepic.droid.databinding.ItemSimpleCourseListDefaultBinding import org.stepik.android.domain.catalog.model.CatalogCourseList import org.stepik.android.view.catalog.mapper.CourseCountMapper import ru.nobird.android.ui.adapterdelegates.AdapterDelegate @@ -22,9 +21,9 @@ class SimpleCourseListDefaultAdapterDelegate( override fun onCreateViewHolder(parent: ViewGroup): DelegateViewHolder = ViewHolder(createView(parent, R.layout.item_simple_course_list_default)) - private inner class ViewHolder( - override val containerView: View - ) : DelegateViewHolder(containerView), LayoutContainer { + private inner class ViewHolder(root: android.view.View) : DelegateViewHolder(root) { + private val viewBinding: ItemSimpleCourseListDefaultBinding by viewBinding { ItemSimpleCourseListDefaultBinding.bind(root) } + private val colorSchemes = listOf( R.color.color_overlay_green, @@ -34,20 +33,20 @@ class SimpleCourseListDefaultAdapterDelegate( ).map { AppCompatResources.getColorStateList(context, it) } init { - containerView.setOnClickListener { onCourseListClicked(itemData ?: return@setOnClickListener) } + root.setOnClickListener { onCourseListClicked(itemData ?: return@setOnClickListener) } } override fun onBind(data: CatalogCourseList) { - simpleCourseListTitle.text = data.title - simpleCourseListCount.text = + viewBinding.simpleCourseListTitle.text = data.title + viewBinding.simpleCourseListCount.text = courseCountMapper.mapCourseCountToString(context, data.coursesCount) val colorList = colorSchemes[adapterPosition % colorSchemes.size] - simpleCourseListTitle.setTextColor(colorList) - simpleCourseListCount.setTextColor(colorList) + viewBinding.simpleCourseListTitle.setTextColor(colorList) + viewBinding.simpleCourseListCount.setTextColor(colorList) ViewCompat.setBackgroundTintList(itemView, colorList) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/SimpleCourseListGridAdapterDelegate.kt b/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/SimpleCourseListGridAdapterDelegate.kt index 4a10c6a792..ce237195e1 100644 --- a/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/SimpleCourseListGridAdapterDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/SimpleCourseListGridAdapterDelegate.kt @@ -1,12 +1,11 @@ package org.stepik.android.view.catalog.ui.adapter.delegate -import android.view.View import android.view.ViewGroup import androidx.core.view.updateLayoutParams +import by.kirich1409.viewbindingdelegate.viewBinding import com.google.android.flexbox.FlexboxLayoutManager -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_block_simple_course_list_grid.* import org.stepic.droid.R +import org.stepic.droid.databinding.ItemBlockSimpleCourseListGridBinding import org.stepik.android.domain.catalog.model.CatalogCourseList import ru.nobird.android.ui.adapterdelegates.AdapterDelegate import ru.nobird.android.ui.adapterdelegates.DelegateViewHolder @@ -20,17 +19,16 @@ class SimpleCourseListGridAdapterDelegate( override fun onCreateViewHolder(parent: ViewGroup): DelegateViewHolder = ViewHolder(createView(parent, R.layout.item_block_simple_course_list_grid)) - private inner class ViewHolder( - override val containerView: View - ) : DelegateViewHolder(containerView), LayoutContainer { + private inner class ViewHolder(root: android.view.View) : DelegateViewHolder(root) { + private val viewBinding: ItemBlockSimpleCourseListGridBinding by viewBinding { ItemBlockSimpleCourseListGridBinding.bind(root) } init { - containerView.setOnClickListener { onCourseListClicked(itemData ?: return@setOnClickListener) } + root.setOnClickListener { onCourseListClicked(itemData ?: return@setOnClickListener) } } override fun onBind(data: CatalogCourseList) { - simpleCourseListGridTitle.text = data.title + viewBinding.simpleCourseListGridTitle.text = data.title itemView.updateLayoutParams { flexGrow = 1f } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/SimpleCourseListGridFirstAdapter.kt b/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/SimpleCourseListGridFirstAdapter.kt index bc7243f6b3..c460a67fea 100644 --- a/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/SimpleCourseListGridFirstAdapter.kt +++ b/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/SimpleCourseListGridFirstAdapter.kt @@ -1,10 +1,9 @@ package org.stepik.android.view.catalog.ui.adapter.delegate -import android.view.View import android.view.ViewGroup -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.item_block_simple_course_list_grid_first.* +import by.kirich1409.viewbindingdelegate.viewBinding import org.stepic.droid.R +import org.stepic.droid.databinding.ItemBlockSimpleCourseListGridFirstBinding import org.stepik.android.domain.catalog.model.CatalogCourseList import org.stepik.android.view.catalog.mapper.CourseCountMapper import ru.nobird.android.ui.adapterdelegates.AdapterDelegate @@ -20,17 +19,17 @@ class SimpleCourseListGridFirstAdapter( override fun onCreateViewHolder(parent: ViewGroup): DelegateViewHolder = ViewHolder(createView(parent, R.layout.item_block_simple_course_list_grid_first)) - private inner class ViewHolder( - override val containerView: View - ) : DelegateViewHolder(containerView), LayoutContainer { + private inner class ViewHolder(root: android.view.View) : DelegateViewHolder(root) { + private val viewBinding: ItemBlockSimpleCourseListGridFirstBinding by viewBinding { ItemBlockSimpleCourseListGridFirstBinding.bind(root) } + init { - simpleCourseListGridOverlay.setOnClickListener { onCourseListClicked(itemData ?: return@setOnClickListener) } + viewBinding.simpleCourseListGridOverlay.setOnClickListener { onCourseListClicked(itemData ?: return@setOnClickListener) } } override fun onBind(data: CatalogCourseList) { - simpleCourseListGridTitle.text = data.title - simpleCourseListGridCount.text = + viewBinding.simpleCourseListGridTitle.text = data.title + viewBinding.simpleCourseListGridCount.text = courseCountMapper.mapCourseCountToString(context, data.coursesCount) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/SimpleCourseListsDefaultAdapterDelegate.kt b/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/SimpleCourseListsDefaultAdapterDelegate.kt index ab36b771f2..6cf19c412a 100644 --- a/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/SimpleCourseListsDefaultAdapterDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/SimpleCourseListsDefaultAdapterDelegate.kt @@ -1,13 +1,11 @@ package org.stepik.android.view.catalog.ui.adapter.delegate -import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.header_catalog_block.* -import kotlinx.android.synthetic.main.item_block_simple_course_lists_default.* +import by.kirich1409.viewbindingdelegate.viewBinding import org.stepic.droid.R +import org.stepic.droid.databinding.ItemBlockSimpleCourseListsDefaultBinding import org.stepic.droid.ui.util.CoursesSnapHelper import org.stepik.android.domain.catalog.model.CatalogCourseList import org.stepik.android.presentation.course_list_redux.model.CatalogBlockStateWrapper @@ -32,11 +30,11 @@ class SimpleCourseListsDefaultAdapterDelegate( override fun onCreateViewHolder(parent: ViewGroup): DelegateViewHolder = ViewHolder(createView(parent, R.layout.item_block_simple_course_lists_default)) - private inner class ViewHolder( - override val containerView: View - ) : DelegateViewHolder(containerView), LayoutContainer { + private inner class ViewHolder(root: android.view.View) : DelegateViewHolder(root) { + private val viewBinding: ItemBlockSimpleCourseListsDefaultBinding by viewBinding { ItemBlockSimpleCourseListsDefaultBinding.bind(root) } + private val catalogBlockTitleDelegate = - CatalogBlockHeaderDelegate(catalogBlockContainer, null) + CatalogBlockHeaderDelegate(viewBinding.catalogBlockHeader.root, null) private val adapter = DefaultDelegateAdapter() .also { @@ -45,7 +43,7 @@ class SimpleCourseListsDefaultAdapterDelegate( init { val rowCount = context.resources.getInteger(R.integer.simple_course_lists_default_rows) - courseListsRecycler.layoutManager = + viewBinding.courseListsRecycler.layoutManager = TableLayoutManager( context, horizontalSpanCount = context.resources.getInteger(R.integer.simple_course_lists_default_columns), @@ -53,12 +51,12 @@ class SimpleCourseListsDefaultAdapterDelegate( orientation = LinearLayoutManager.HORIZONTAL, reverseLayout = false ) - courseListsRecycler.setRecycledViewPool(sharedViewPool) - courseListsRecycler.setHasFixedSize(true) - courseListsRecycler.adapter = adapter + viewBinding.courseListsRecycler.setRecycledViewPool(sharedViewPool) + viewBinding.courseListsRecycler.setHasFixedSize(true) + viewBinding.courseListsRecycler.adapter = adapter val snapHelper = CoursesSnapHelper(rowCount) - snapHelper.attachToRecyclerView(courseListsRecycler) + snapHelper.attachToRecyclerView(viewBinding.courseListsRecycler) } override fun onBind(data: CatalogItem) { @@ -74,4 +72,4 @@ class SimpleCourseListsDefaultAdapterDelegate( catalogBlockTitleDelegate.setCount(context.resources.getQuantityString(R.plurals.catalog_course_lists, count, count)) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/SimpleCourseListsGridAdapterDelegate.kt b/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/SimpleCourseListsGridAdapterDelegate.kt index e09588b244..d0dcf2e23a 100644 --- a/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/SimpleCourseListsGridAdapterDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/catalog/ui/adapter/delegate/SimpleCourseListsGridAdapterDelegate.kt @@ -1,16 +1,14 @@ package org.stepik.android.view.catalog.ui.adapter.delegate -import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView +import by.kirich1409.viewbindingdelegate.viewBinding import com.google.android.flexbox.FlexboxLayoutManager import com.google.android.flexbox.FlexDirection import com.google.android.flexbox.FlexWrap import com.google.android.flexbox.JustifyContent -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.header_catalog_block.* -import kotlinx.android.synthetic.main.item_block_simple_course_lists_grid.courseListsRecycler import org.stepic.droid.R +import org.stepic.droid.databinding.ItemBlockSimpleCourseListsGridBinding import org.stepik.android.domain.catalog.model.CatalogCourseList import org.stepik.android.presentation.course_list_redux.model.CatalogBlockStateWrapper import org.stepik.android.view.catalog.mapper.CourseCountMapper @@ -33,11 +31,11 @@ class SimpleCourseListsGridAdapterDelegate( override fun onCreateViewHolder(parent: ViewGroup): DelegateViewHolder = ViewHolder(createView(parent, R.layout.item_block_simple_course_lists_grid)) - private inner class ViewHolder( - override val containerView: View - ) : DelegateViewHolder(containerView), LayoutContainer { + private inner class ViewHolder(root: android.view.View) : DelegateViewHolder(root) { + private val viewBinding: ItemBlockSimpleCourseListsGridBinding by viewBinding { ItemBlockSimpleCourseListsGridBinding.bind(root) } + private val catalogBlockTitleDelegate = - CatalogBlockHeaderDelegate(catalogBlockContainer, null) + CatalogBlockHeaderDelegate(viewBinding.catalogBlockHeader.root, null) private val adapter = DefaultDelegateAdapter() .also { @@ -46,13 +44,13 @@ class SimpleCourseListsGridAdapterDelegate( } init { - courseListsRecycler.layoutManager = + viewBinding.courseListsRecycler.layoutManager = FlexboxLayoutManager(context, FlexDirection.ROW, FlexWrap.WRAP) .apply { justifyContent = JustifyContent.FLEX_START } - courseListsRecycler.setRecycledViewPool(sharedViewPool) - courseListsRecycler.setHasFixedSize(true) - courseListsRecycler.adapter = adapter + viewBinding.courseListsRecycler.setRecycledViewPool(sharedViewPool) + viewBinding.courseListsRecycler.setHasFixedSize(true) + viewBinding.courseListsRecycler.adapter = adapter } override fun onBind(data: CatalogItem) { @@ -69,4 +67,4 @@ class SimpleCourseListsGridAdapterDelegate( catalogBlockTitleDelegate.setCount(context.resources.getQuantityString(R.plurals.catalog_course_lists, count, count)) } } -} \ No newline at end of file +} diff --git a/app/src/main/res/layout/item_block_simple_course_list_grid.xml b/app/src/main/res/layout/item_block_simple_course_list_grid.xml index afff7dbb32..7336632a4b 100644 --- a/app/src/main/res/layout/item_block_simple_course_list_grid.xml +++ b/app/src/main/res/layout/item_block_simple_course_list_grid.xml @@ -6,8 +6,7 @@ android:padding="16dp" android:background="@drawable/bg_catalog_course_list_grid" android:layout_margin="@dimen/course_item_margin" - tools:layout_width="154dp" - tools:viewBindingIgnore="true"> + tools:layout_width="154dp"> + tools:layout_width="320dp"> + android:orientation="vertical"> - + + android:orientation="vertical"> - + + tools:layout_width="154dp"> Date: Wed, 3 Jun 2026 00:20:14 +0200 Subject: [PATCH 14/65] refactor: migrate 5 files from synthetic to viewBinding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ToolbarHelper.kt (extension functions → findViewById) - LessonDemoCompleteBottomSheetDialogFragment.kt - SettingsFragment.kt - InAppWebViewDialogFragment.kt (complex: multiple includes, manual WebView) - CourseContentTimelineAdapter.kt (ViewHolder lambda form) Also removed tools:viewBindingIgnore from 4 layouts and added include IDs to dialog_in_app_web_view.xml Co-Authored-By: Claude Opus 4.8 --- .../org/stepic/droid/ui/util/ToolbarHelper.kt | 11 ++- .../adapter/CourseContentTimelineAdapter.kt | 22 +++-- .../ui/dialog/InAppWebViewDialogFragment.kt | 59 ++++++------- ...onDemoCompleteBottomSheetDialogFragment.kt | 30 +++---- .../settings/ui/fragment/SettingsFragment.kt | 83 ++++++++++--------- ...ttom_sheet_dialog_lesson_demo_complete.xml | 2 +- .../res/layout/dialog_in_app_web_view.xml | 10 ++- app/src/main/res/layout/fragment_settings.xml | 4 +- .../view_course_content_section_date.xml | 2 +- 9 files changed, 118 insertions(+), 105 deletions(-) diff --git a/app/src/main/java/org/stepic/droid/ui/util/ToolbarHelper.kt b/app/src/main/java/org/stepic/droid/ui/util/ToolbarHelper.kt index fbc4f721d6..0d16f7c7c9 100644 --- a/app/src/main/java/org/stepic/droid/ui/util/ToolbarHelper.kt +++ b/app/src/main/java/org/stepic/droid/ui/util/ToolbarHelper.kt @@ -1,5 +1,6 @@ package org.stepic.droid.ui.util +import android.widget.TextView import androidx.annotation.AttrRes import androidx.annotation.DrawableRes import androidx.annotation.StringRes @@ -7,11 +8,19 @@ import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.widget.Toolbar import androidx.fragment.app.Fragment -import kotlinx.android.synthetic.main.view_centered_toolbar.* import org.stepic.droid.R import org.stepik.android.view.base.ui.extension.setTintList +private val Fragment.centeredToolbarTitle: TextView + get() = requireActivity().findViewById(R.id.centeredToolbarTitle) + +private val AppCompatActivity.centeredToolbar: Toolbar + get() = findViewById(R.id.centeredToolbar) + +private val AppCompatActivity.centeredToolbarTitle: TextView + get() = findViewById(R.id.centeredToolbarTitle) + //Fragment's functions: @JvmOverloads diff --git a/app/src/main/java/org/stepik/android/view/course_content/ui/adapter/CourseContentTimelineAdapter.kt b/app/src/main/java/org/stepik/android/view/course_content/ui/adapter/CourseContentTimelineAdapter.kt index 838031dc97..b65ab0a974 100644 --- a/app/src/main/java/org/stepik/android/view/course_content/ui/adapter/CourseContentTimelineAdapter.kt +++ b/app/src/main/java/org/stepik/android/view/course_content/ui/adapter/CourseContentTimelineAdapter.kt @@ -4,8 +4,9 @@ import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView -import kotlinx.android.synthetic.main.view_course_content_section_date.view.* +import by.kirich1409.viewbindingdelegate.viewBinding import org.stepic.droid.R +import org.stepic.droid.databinding.ViewCourseContentSectionDateBinding import org.stepic.droid.util.DateTimeHelper import org.stepic.droid.util.safeDiv import org.stepik.android.view.course_content.model.CourseContentSectionDate @@ -33,25 +34,22 @@ class CourseContentTimelineAdapter : RecyclerView.Adapter= data.date + viewBinding.dateDot.isEnabled = now >= data.date } } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/stepik/android/view/in_app_web_view/ui/dialog/InAppWebViewDialogFragment.kt b/app/src/main/java/org/stepik/android/view/in_app_web_view/ui/dialog/InAppWebViewDialogFragment.kt index db294f4eaf..23d6760d07 100644 --- a/app/src/main/java/org/stepik/android/view/in_app_web_view/ui/dialog/InAppWebViewDialogFragment.kt +++ b/app/src/main/java/org/stepik/android/view/in_app_web_view/ui/dialog/InAppWebViewDialogFragment.kt @@ -24,16 +24,13 @@ import androidx.core.view.isVisible import androidx.fragment.app.DialogFragment import androidx.fragment.app.viewModels import androidx.lifecycle.ViewModelProvider +import by.kirich1409.viewbindingdelegate.viewBinding import com.google.android.material.dialog.MaterialAlertDialogBuilder -import kotlinx.android.synthetic.main.dialog_in_app_web_view.* -import kotlinx.android.synthetic.main.dialog_in_app_web_view.view.* -import kotlinx.android.synthetic.main.error_no_connection_with_button.* -import kotlinx.android.synthetic.main.progress_bar_on_empty_screen.* -import kotlinx.android.synthetic.main.view_centered_toolbar.* import org.stepic.droid.R import org.stepic.droid.base.App import org.stepic.droid.configuration.EndpointResolver import org.stepic.droid.core.ScreenManager +import org.stepic.droid.databinding.DialogInAppWebViewBinding import org.stepic.droid.ui.util.setTintedNavigationIcon import org.stepik.android.presentation.in_app_web_view.InAppWebViewPresenter import org.stepik.android.presentation.in_app_web_view.InAppWebViewView @@ -76,6 +73,8 @@ class InAppWebViewDialogFragment : DialogFragment(), InAppWebViewView { @Inject internal lateinit var endpointResolver: EndpointResolver + private val binding: DialogInAppWebViewBinding by viewBinding(DialogInAppWebViewBinding::bind) + private val inAppWebViewPresenter: InAppWebViewPresenter by viewModels { viewModelFactory } private var title: String by argument() @@ -162,40 +161,42 @@ class InAppWebViewDialogFragment : DialogFragment(), InAppWebViewView { } } } - webView?.let { root.containerView.addView(it) } + webView?.let { root.findViewById(R.id.containerView).addView(it) } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { viewStateDelegate = ViewStateDelegate() viewStateDelegate.addState() - viewStateDelegate.addState(loadProgressbarOnEmptyScreen) - viewStateDelegate.addState(loadProgressbarOnEmptyScreen) - viewStateDelegate.addState(error) + viewStateDelegate.addState(binding.progressBarOnEmptyScreen.root) + viewStateDelegate.addState(binding.progressBarOnEmptyScreen.root) + viewStateDelegate.addState(binding.errorNoConnection.error) viewStateDelegate.addState(webView as View) - centeredToolbarTitle.text = title - centeredToolbar.setNavigationOnClickListener { - if (showsDialog) { - dismiss() - } else { - activity?.finish() + with(binding.centeredAppbar.centeredToolbarContainer) { + centeredToolbarTitle.text = title + centeredToolbar.setNavigationOnClickListener { + if (showsDialog) { + dismiss() + } else { + activity?.finish() + } } - } - centeredToolbar.setTintedNavigationIcon(R.drawable.ic_close_dark) - centeredToolbar.inflateMenu(R.menu.in_app_web_view_menu) - centeredToolbar.setOnMenuItemClickListener { menuItem -> - when (menuItem.itemId) { - R.id.menu_item_external -> { - val externalUrl = webView?.url ?: url - screenManager.openLinkInWebBrowser(requireContext(), Uri.parse(externalUrl)) - true + centeredToolbar.setTintedNavigationIcon(R.drawable.ic_close_dark) + centeredToolbar.inflateMenu(R.menu.in_app_web_view_menu) + centeredToolbar.setOnMenuItemClickListener { menuItem -> + when (menuItem.itemId) { + R.id.menu_item_external -> { + val externalUrl = webView?.url ?: url + screenManager.openLinkInWebBrowser(requireContext(), Uri.parse(externalUrl)) + true + } + else -> + super.onOptionsItemSelected(menuItem) } - else -> - super.onOptionsItemSelected(menuItem) } } - tryAgain.setOnClickListener { setDataToPresenter(forceUpdate = true) } + binding.errorNoConnection.tryAgain.setOnClickListener { setDataToPresenter(forceUpdate = true) } setDataToPresenter() } @@ -229,7 +230,7 @@ class InAppWebViewDialogFragment : DialogFragment(), InAppWebViewView { } override fun onDestroyView() { - containerView.removeView(webView) + binding.containerView.removeView(webView) super.onDestroyView() } @@ -319,4 +320,4 @@ class InAppWebViewDialogFragment : DialogFragment(), InAppWebViewView { interface Callback { fun onDismissed() } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/stepik/android/view/lesson_demo/ui/dialog/LessonDemoCompleteBottomSheetDialogFragment.kt b/app/src/main/java/org/stepik/android/view/lesson_demo/ui/dialog/LessonDemoCompleteBottomSheetDialogFragment.kt index 309068027a..6bdecdf54f 100644 --- a/app/src/main/java/org/stepik/android/view/lesson_demo/ui/dialog/LessonDemoCompleteBottomSheetDialogFragment.kt +++ b/app/src/main/java/org/stepik/android/view/lesson_demo/ui/dialog/LessonDemoCompleteBottomSheetDialogFragment.kt @@ -6,17 +6,17 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.DialogFragment import androidx.lifecycle.ViewModelProvider +import by.kirich1409.viewbindingdelegate.viewBinding import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.firebase.remoteconfig.FirebaseRemoteConfig import com.google.firebase.remoteconfig.ktx.get -import kotlinx.android.synthetic.main.bottom_sheet_dialog_lesson_demo_complete.* -import kotlinx.android.synthetic.main.error_no_connection_with_button_small.* import org.stepic.droid.R import org.stepic.droid.base.App import org.stepic.droid.configuration.RemoteConfig import org.stepic.droid.core.ScreenManager +import org.stepic.droid.databinding.BottomSheetDialogLessonDemoCompleteBinding import org.stepik.android.domain.course.analytic.CourseViewSource import org.stepik.android.domain.course_payments.model.DeeplinkPromoCode import org.stepik.android.domain.course_payments.model.DefaultPromoCode @@ -69,6 +69,8 @@ class LessonDemoCompleteBottomSheetDialogFragment : @Inject internal lateinit var firebaseRemoteConfig: FirebaseRemoteConfig + private val binding: BottomSheetDialogLessonDemoCompleteBinding by viewBinding(BottomSheetDialogLessonDemoCompleteBinding::bind) + private val lessonDemoViewModel: LessonDemoViewModel by reduxViewModel(this) { viewModelFactory } private val viewStateDelegate = ViewStateDelegate() private lateinit var wishlistViewDelegate: WishlistViewDelegate @@ -96,19 +98,19 @@ class LessonDemoCompleteBottomSheetDialogFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) initViewStateDelegate() - wishlistViewDelegate = WishlistViewDelegate(demoWishlistAction) - demoCompleteTitle.text = getString(R.string.demo_complete_title, course.title) - demoCompleteAction.setOnClickListener { + wishlistViewDelegate = WishlistViewDelegate(binding.demoWishlistAction) + binding.demoCompleteTitle.text = getString(R.string.demo_complete_title, course.title) + binding.demoCompleteAction.setOnClickListener { lessonDemoViewModel.onNewMessage(LessonDemoFeature.Message.BuyActionMessage) } - demoWishlistAction.setOnClickListener { + binding.demoWishlistAction.setOnClickListener { lessonDemoViewModel.onNewMessage( LessonDemoFeature.Message.WishlistMessage( WishlistOperationFeature.Message.WishlistAddMessage(course, CourseViewSource.LessonDemoDialog) ) ) } - tryAgain.setOnClickListener { lessonDemoViewModel.onNewMessage(LessonDemoFeature.Message.InitMessage(forceUpdate = true)) } + binding.demoCompleteNetworkError.tryAgain.setOnClickListener { lessonDemoViewModel.onNewMessage(LessonDemoFeature.Message.InitMessage(forceUpdate = true)) } } override fun onAction(action: LessonDemoFeature.Action.ViewAction) { @@ -157,10 +159,10 @@ class LessonDemoCompleteBottomSheetDialogFragment : private fun initViewStateDelegate() { viewStateDelegate.addState() - viewStateDelegate.addState(demoCompleteProgressbar) - viewStateDelegate.addState(demoCompleteNetworkError) - viewStateDelegate.addState(demoCompleteContent, demoCompleteTitle, demoPurchaseUnavailable, demoWishlistAction) - viewStateDelegate.addState(demoCompleteContent, demoCompleteTitle, demoCompleteInfo, demoCompleteDivider, demoCompleteAction, demoWishlistAction) + viewStateDelegate.addState(binding.demoCompleteProgressbar) + viewStateDelegate.addState(binding.demoCompleteNetworkError.root) + viewStateDelegate.addState(binding.demoCompleteContent, binding.demoCompleteTitle, binding.demoPurchaseUnavailable, binding.demoWishlistAction) + viewStateDelegate.addState(binding.demoCompleteContent, binding.demoCompleteTitle, binding.demoCompleteInfo, binding.demoCompleteDivider.root, binding.demoCompleteAction, binding.demoWishlistAction) } private fun setupWeb(deeplinkPromoCode: DeeplinkPromoCode) { @@ -175,7 +177,7 @@ class LessonDemoCompleteBottomSheetDialogFragment : ), course ) - demoCompleteAction.text = + binding.demoCompleteAction.text = if (courseDisplayPrice != null) { if (hasPromo) { displayPriceMapper.mapToDiscountedDisplayPriceSpannedString(courseDisplayPrice, promoPrice, currencyCode) @@ -189,7 +191,7 @@ class LessonDemoCompleteBottomSheetDialogFragment : private fun setupIAP(coursePurchaseData: CoursePurchaseData) { val courseDisplayPrice = coursePurchaseData.course.displayPrice - demoCompleteAction.text = + binding.demoCompleteAction.text = if (courseDisplayPrice != null) { if (coursePurchaseData.promoCodeSku.lightSku != null) { displayPriceMapper.mapToDiscountedDisplayPriceSpannedString(coursePurchaseData.primarySku.price, coursePurchaseData.promoCodeSku.lightSku.price) @@ -204,4 +206,4 @@ class LessonDemoCompleteBottomSheetDialogFragment : override fun continueLearning() { screenManager.showCourseAfterPurchase(requireContext(), course, CourseViewSource.LessonDemoDialog, CourseScreenTab.INFO) } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/stepik/android/view/settings/ui/fragment/SettingsFragment.kt b/app/src/main/java/org/stepik/android/view/settings/ui/fragment/SettingsFragment.kt index eedfc1dde2..219c9dfc2a 100644 --- a/app/src/main/java/org/stepik/android/view/settings/ui/fragment/SettingsFragment.kt +++ b/app/src/main/java/org/stepik/android/view/settings/ui/fragment/SettingsFragment.kt @@ -6,13 +6,14 @@ import androidx.fragment.app.DialogFragment import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.lifecycle.ViewModelProvider +import by.kirich1409.viewbindingdelegate.viewBinding import com.vk.api.sdk.VK -import kotlinx.android.synthetic.main.fragment_settings.* import org.stepic.droid.R import org.stepic.droid.analytic.AmplitudeAnalytic import org.stepic.droid.analytic.Analytic import org.stepic.droid.base.App import org.stepic.droid.core.ScreenManager +import org.stepic.droid.databinding.FragmentSettingsBinding import org.stepic.droid.preferences.SharedPreferenceHelper import org.stepic.droid.preferences.UserPreferences import org.stepic.droid.ui.dialogs.AllowMobileDataDialogFragment @@ -43,6 +44,8 @@ class SettingsFragment : SettingsFragment() } + private val binding: FragmentSettingsBinding by viewBinding(FragmentSettingsBinding::bind) + private val presenter: SettingsPresenter by viewModels { viewModelFactory } private val progressDialogFragment: DialogFragment = @@ -74,41 +77,41 @@ class SettingsFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - notificationActionButton.setOnClickListener { + binding.notificationActionButton.setOnClickListener { screenManager.showNotificationSettings(activity) } - fragmentSettingsWifiEnableSwitch.isChecked = !sharedPreferenceHelper.isMobileInternetAlsoAllowed // if first time it is true + binding.fragmentSettingsWifiEnableSwitch.isChecked = !sharedPreferenceHelper.isMobileInternetAlsoAllowed // if first time it is true - fragmentSettingsExternalPlayerSwitch.isChecked = userPreferences.isOpenInExternal + binding.fragmentSettingsExternalPlayerSwitch.isChecked = userPreferences.isOpenInExternal - fragmentSettingsExternalPlayerSwitch.setOnCheckedChangeListener { _, isChecked -> userPreferences.isOpenInExternal = isChecked } + binding.fragmentSettingsExternalPlayerSwitch.setOnCheckedChangeListener { _, isChecked -> userPreferences.isOpenInExternal = isChecked } - fragmentSettingsCalendarWidgetSwitch.isChecked = userPreferences.isNeedToShowCalendarWidget + binding.fragmentSettingsCalendarWidgetSwitch.isChecked = userPreferences.isNeedToShowCalendarWidget - fragmentSettingsCalendarWidgetSwitch.setOnCheckedChangeListener { _, isChecked -> userPreferences.isNeedToShowCalendarWidget = isChecked } + binding.fragmentSettingsCalendarWidgetSwitch.setOnCheckedChangeListener { _, isChecked -> userPreferences.isNeedToShowCalendarWidget = isChecked } - fragmentSettingsKeepScreenOnSwitch.isChecked = userPreferences.isKeepScreenOnSteps - fragmentSettingsKeepScreenOnSwitch.setOnCheckedChangeListener { _, isChecked -> userPreferences.isKeepScreenOnSteps = isChecked } + binding.fragmentSettingsKeepScreenOnSwitch.isChecked = userPreferences.isKeepScreenOnSteps + binding.fragmentSettingsKeepScreenOnSwitch.setOnCheckedChangeListener { _, isChecked -> userPreferences.isKeepScreenOnSteps = isChecked } - fragmentSettingsAdaptiveMode.isChecked = userPreferences.isAdaptiveModeEnabled - fragmentSettingsAdaptiveMode.setOnCheckedChangeListener { _, isChecked -> userPreferences.isAdaptiveModeEnabled = isChecked } + binding.fragmentSettingsAdaptiveMode.isChecked = userPreferences.isAdaptiveModeEnabled + binding.fragmentSettingsAdaptiveMode.setOnCheckedChangeListener { _, isChecked -> userPreferences.isAdaptiveModeEnabled = isChecked } - fragmentSettingsDiscountingPolicySwitch.isChecked = userPreferences.isShowDiscountingPolicyWarning + binding.fragmentSettingsDiscountingPolicySwitch.isChecked = userPreferences.isShowDiscountingPolicyWarning - fragmentSettingsDiscountingPolicySwitch.setOnCheckedChangeListener { _, isChecked -> userPreferences.isShowDiscountingPolicyWarning = isChecked } + binding.fragmentSettingsDiscountingPolicySwitch.setOnCheckedChangeListener { _, isChecked -> userPreferences.isShowDiscountingPolicyWarning = isChecked } - fragmentSettingsAutoplay.isChecked = userPreferences.isAutoplayEnabled - fragmentSettingsAutoplay.setOnCheckedChangeListener { _, isChecked -> userPreferences.isAutoplayEnabled = isChecked } + binding.fragmentSettingsAutoplay.isChecked = userPreferences.isAutoplayEnabled + binding.fragmentSettingsAutoplay.setOnCheckedChangeListener { _, isChecked -> userPreferences.isAutoplayEnabled = isChecked } - fragmentSettingsWifiEnableSwitch.setOnCheckedChangeListener { _, newCheckedState -> - if (fragmentSettingsWifiEnableSwitch.isUserTriggered) { + binding.fragmentSettingsWifiEnableSwitch.setOnCheckedChangeListener { _, newCheckedState -> + if (binding.fragmentSettingsWifiEnableSwitch.isUserTriggered) { if (newCheckedState) { // wifi only onMobileDataStateChanged(false) } else { // wifi and mobile internet - fragmentSettingsWifiEnableSwitch.isChecked = true + binding.fragmentSettingsWifiEnableSwitch.isChecked = true val dialogFragment = AllowMobileDataDialogFragment.newInstance() dialogFragment.setTargetFragment(this@SettingsFragment, 0) dialogFragment.showIfNotExists(parentFragmentManager, AllowMobileDataDialogFragment.TAG) @@ -116,67 +119,67 @@ class SettingsFragment : } } - videoQualityView.setOnClickListener { + binding.videoQualityView.setOnClickListener { VideoQualityDialog .newInstance(forPlaying = false) .showIfNotExists(childFragmentManager, VideoQualityDialog.TAG) } - videoPlayingQualityView.setOnClickListener { + binding.videoPlayingQualityView.setOnClickListener { VideoQualityDialog .newInstance(forPlaying = true) .showIfNotExists(childFragmentManager, VideoQualityDialog.TAG) } - storageManagementButton.setOnClickListener { screenManager.showStorageManagement(activity) } + binding.storageManagementButton.setOnClickListener { screenManager.showStorageManagement(activity) } - langWidgetActionButton.setOnClickListener { + binding.langWidgetActionButton.setOnClickListener { CoursesLangDialogFragment .newInstance() .showIfNotExists(childFragmentManager, CoursesLangDialogFragment.TAG) } - nightModeSettingsButton.setOnClickListener { + binding.nightModeSettingsButton.setOnClickListener { NightModeSettingDialogFragment .newInstance() .showIfNotExists(childFragmentManager, NightModeSettingDialogFragment.TAG) } - fontSizeSettingsButton.setOnClickListener { + binding.fontSizeSettingsButton.setOnClickListener { ChooseFontSizeDialogFragment .newInstance() .showIfNotExists(childFragmentManager, ChooseFontSizeDialogFragment.TAG) } - downloadsSettingsButton.setOnClickListener { + binding.downloadsSettingsButton.setOnClickListener { analytic.reportEvent(Analytic.Screens.USER_OPEN_DOWNLOADS) screenManager.showDownloads(requireContext()) } - contactSupportButton.setOnClickListener { + binding.contactSupportButton.setOnClickListener { presenter.contactSupport( getString(R.string.feedback_subject), DeviceInfoUtil.getInfosAboutDevice(context, "\n") ) } - helpCenterButton.setOnClickListener { + binding.helpCenterButton.setOnClickListener { InAppWebViewDialogFragment .newInstance(getString(R.string.settings_help_center), getString(R.string.settings_help_center_url)) .showIfNotExists(childFragmentManager, InAppWebViewDialogFragment.TAG) } - feedbackSettingsButton.setOnClickListener { + binding.feedbackSettingsButton.setOnClickListener { analytic.reportEvent(Analytic.Screens.USER_OPEN_FEEDBACK) screenManager.openFeedbackActivity(requireActivity()) } - aboutSettingsButton.setOnClickListener { + binding.aboutSettingsButton.setOnClickListener { analytic.reportEvent(Analytic.Screens.USER_OPEN_ABOUT_APP) screenManager.openAboutActivity(requireActivity()) } - deleteAccountButton.setOnClickListener { + binding.deleteAccountButton.setOnClickListener { analytic.reportAmplitudeEvent(AmplitudeAnalytic.Settings.DELETE_ACCOUNT_CLICKED) InAppWebViewDialogFragment .newInstance( @@ -187,7 +190,7 @@ class SettingsFragment : .showIfNotExists(childFragmentManager, InAppWebViewDialogFragment.TAG) } - logoutSettingsButton.setOnClickListener { + binding.logoutSettingsButton.setOnClickListener { val supportFragmentManager = activity ?.supportFragmentManager ?: return@setOnClickListener @@ -217,13 +220,13 @@ class SettingsFragment : } override fun onDestroyView() { - fragmentSettingsKeepScreenOnSwitch.setOnCheckedChangeListener(null) - fragmentSettingsDiscountingPolicySwitch.setOnCheckedChangeListener(null) - fragmentSettingsCalendarWidgetSwitch.setOnCheckedChangeListener(null) - fragmentSettingsWifiEnableSwitch.setOnCheckedChangeListener(null) - fragmentSettingsExternalPlayerSwitch.setOnCheckedChangeListener(null) - storageManagementButton.setOnClickListener(null) - notificationActionButton.setOnClickListener(null) + binding.fragmentSettingsKeepScreenOnSwitch.setOnCheckedChangeListener(null) + binding.fragmentSettingsDiscountingPolicySwitch.setOnCheckedChangeListener(null) + binding.fragmentSettingsCalendarWidgetSwitch.setOnCheckedChangeListener(null) + binding.fragmentSettingsWifiEnableSwitch.setOnCheckedChangeListener(null) + binding.fragmentSettingsExternalPlayerSwitch.setOnCheckedChangeListener(null) + binding.storageManagementButton.setOnClickListener(null) + binding.notificationActionButton.setOnClickListener(null) super.onDestroyView() } @@ -232,7 +235,7 @@ class SettingsFragment : } override fun onMobileDataStateChanged(isMobileAllowed: Boolean) { - fragmentSettingsWifiEnableSwitch.isChecked = !isMobileAllowed + binding.fragmentSettingsWifiEnableSwitch.isChecked = !isMobileAllowed storeMobileState(isMobileAllowed) } @@ -273,4 +276,4 @@ class SettingsFragment : interface SignOutListener { fun onSignOut() } -} \ No newline at end of file +} diff --git a/app/src/main/res/layout/bottom_sheet_dialog_lesson_demo_complete.xml b/app/src/main/res/layout/bottom_sheet_dialog_lesson_demo_complete.xml index 578f809ff9..e4042fa7f7 100644 --- a/app/src/main/res/layout/bottom_sheet_dialog_lesson_demo_complete.xml +++ b/app/src/main/res/layout/bottom_sheet_dialog_lesson_demo_complete.xml @@ -4,7 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - tools:viewBindingIgnore="true"> + > + android:orientation="vertical"> - + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml index 197e17e953..678646a84a 100644 --- a/app/src/main/res/layout/fragment_settings.xml +++ b/app/src/main/res/layout/fragment_settings.xml @@ -1,11 +1,9 @@ + android:scrollbarStyle="outsideOverlay"> + > Date: Wed, 3 Jun 2026 00:28:18 +0200 Subject: [PATCH 15/65] refactor: migrate 5 files from synthetic to viewBinding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CourseContentUnitDelegate.kt (ViewHolder → viewBinding delegate) - CourseContentControlBarDelegate.kt (ViewHolder → viewBinding delegate) - CourseContentSectionDelegate.kt (ViewHolder → viewBinding delegate) - CourseContentFragment.kt (Fragment with multiple includes) - DownloadStatusView.kt (Custom View → binding.inflate) Also removed tools:viewBindingIgnore from 7 layouts and added include IDs to fragment_course_content.xml for error_no_connection and empty_default includes. Co-Authored-By: Claude Opus 4.8 --- .../CourseContentControlBarDelegate.kt | 8 +- .../section/CourseContentSectionDelegate.kt | 108 ++++++++---------- .../unit/CourseContentUnitDelegate.kt | 72 +++++------- .../ui/fragment/CourseContentFragment.kt | 21 ++-- .../ui/view/DownloadStatusView.kt | 18 +-- app/src/main/res/layout/empty_default.xml | 2 +- .../main/res/layout/error_no_connection.xml | 2 +- .../res/layout/fragment_course_content.xml | 5 +- .../view_course_content_control_bar.xml | 3 +- .../layout/view_course_content_section.xml | 2 +- .../res/layout/view_course_content_unit.xml | 2 +- .../main/res/layout/view_download_status.xml | 2 +- 12 files changed, 114 insertions(+), 131 deletions(-) diff --git a/app/src/main/java/org/stepik/android/view/course_content/ui/adapter/delegates/control_bar/CourseContentControlBarDelegate.kt b/app/src/main/java/org/stepik/android/view/course_content/ui/adapter/delegates/control_bar/CourseContentControlBarDelegate.kt index 6c9705900a..9420c36e97 100644 --- a/app/src/main/java/org/stepik/android/view/course_content/ui/adapter/delegates/control_bar/CourseContentControlBarDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/course_content/ui/adapter/delegates/control_bar/CourseContentControlBarDelegate.kt @@ -7,8 +7,9 @@ import android.widget.TextView import androidx.appcompat.widget.PopupMenu import androidx.collection.LongSparseArray import androidx.swiperefreshlayout.widget.CircularProgressDrawable -import kotlinx.android.synthetic.main.view_course_content_control_bar.view.* +import by.kirich1409.viewbindingdelegate.viewBinding import org.stepic.droid.R +import org.stepic.droid.databinding.ViewCourseContentControlBarBinding import org.stepic.droid.persistence.model.DownloadProgress import org.stepic.droid.ui.util.setHeight import org.stepic.droid.util.TextUtil @@ -32,8 +33,9 @@ class CourseContentControlBarDelegate( data is CourseContentItem.ControlBar inner class ViewHolder(root: View) : DelegateViewHolder(root) { + private val viewBinding: ViewCourseContentControlBarBinding by viewBinding { ViewCourseContentControlBarBinding.bind(root) } - private val controlBar = root.controlBar + private val controlBar = viewBinding.controlBar private lateinit var status: DownloadProgress.Status private lateinit var downloadControl: View private lateinit var downloadDrawable: ImageView @@ -163,4 +165,4 @@ class CourseContentControlBarDelegate( } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/stepik/android/view/course_content/ui/adapter/delegates/section/CourseContentSectionDelegate.kt b/app/src/main/java/org/stepik/android/view/course_content/ui/adapter/delegates/section/CourseContentSectionDelegate.kt index 4d45579bf0..d278d3202d 100644 --- a/app/src/main/java/org/stepik/android/view/course_content/ui/adapter/delegates/section/CourseContentSectionDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/course_content/ui/adapter/delegates/section/CourseContentSectionDelegate.kt @@ -14,8 +14,9 @@ import androidx.core.content.ContextCompat import androidx.core.graphics.drawable.DrawableCompat import androidx.core.view.isVisible import androidx.recyclerview.widget.LinearLayoutManager -import kotlinx.android.synthetic.main.view_course_content_section.view.* +import by.kirich1409.viewbindingdelegate.viewBinding import org.stepic.droid.R +import org.stepic.droid.databinding.ViewCourseContentSectionBinding import org.stepic.droid.persistence.model.DownloadProgress import org.stepic.droid.ui.util.StartSnapHelper import org.stepic.droid.util.toFixed @@ -40,41 +41,32 @@ class CourseContentSectionDelegate( data is CourseContentItem.SectionItem inner class ViewHolder(root: View) : DelegateViewHolder(root) { - private val sectionExamType = root.sectionExamType - private val sectionExamStatus = root.sectionExamStatus - private val sectionTitle = root.sectionTitle - private val sectionPosition = root.sectionPosition - private val sectionTimeline = root.sectionTimeline - private val sectionProgress = root.sectionProgress - private val sectionTextProgress = root.sectionTextProgress - private val sectionDownloadStatus = root.sectionDownloadStatus - private val sectionRequirementsDescription = root.sectionRequirementsDescription - private val sectionExamAction = root.sectionExamAction + private val viewBinding: ViewCourseContentSectionBinding by viewBinding { ViewCourseContentSectionBinding.bind(root) } private val sectionTimeLineAdapter = CourseContentTimelineAdapter() init { - with(sectionTimeline) { + with(viewBinding.sectionTimeline) { adapter = sectionTimeLineAdapter layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) StartSnapHelper().attachToRecyclerView(this) addItemDecoration(CourseContentTimelineDecorator()) - this@ViewHolder.sectionTitle.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener { + this@ViewHolder.viewBinding.sectionTitle.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener { override fun onPreDraw(): Boolean { - setPadding(this@ViewHolder.sectionTitle.left, paddingTop, paddingRight, paddingBottom) + setPadding(this@ViewHolder.viewBinding.sectionTitle.left, paddingTop, paddingRight, paddingBottom) layoutManager?.scrollToPosition(0) - this@ViewHolder.sectionTitle.viewTreeObserver.removeOnPreDrawListener(this) + this@ViewHolder.viewBinding.sectionTitle.viewTreeObserver.removeOnPreDrawListener(this) return true } }) } - sectionDownloadStatus.setOnClickListener { + viewBinding.sectionDownloadStatus.setOnClickListener { val item = (itemData as? CourseContentItem.SectionItem) ?: return@setOnClickListener - when (sectionDownloadStatus.status) { + when (viewBinding.sectionDownloadStatus.status) { DownloadProgress.Status.NotCached -> sectionClickListener.onItemDownloadClicked(item) @@ -88,7 +80,7 @@ class CourseContentSectionDelegate( } } - sectionExamAction.setOnClickListener { + viewBinding.sectionExamAction.setOnClickListener { val item = (itemData as? CourseContentItem.SectionItem) ?: return@setOnClickListener sectionClickListener.onItemClicked(item) } @@ -99,11 +91,11 @@ class CourseContentSectionDelegate( setupExamViews(data) - sectionExamType.isVisible = section.isExam - sectionExamStatus.isVisible = section.isExam + viewBinding.sectionExamType.isVisible = section.isExam + viewBinding.sectionExamStatus.isVisible = section.isExam - sectionTitle.text = section.title - sectionPosition.text = section.position.toString() + viewBinding.sectionTitle.text = section.title + viewBinding.sectionPosition.text = section.position.toString() if (progress != null) { when { @@ -113,61 +105,61 @@ class CourseContentSectionDelegate( ?.toFloatOrNull() ?: 0f - sectionProgress.progress = score / progress.cost.toFloat() - sectionTextProgress.text = context.resources.getString(R.string.course_content_text_progress_points, + viewBinding.sectionProgress.progress = score / progress.cost.toFloat() + viewBinding.sectionTextProgress.text = context.resources.getString(R.string.course_content_text_progress_points, score.toFixed(context.resources.getInteger(R.integer.score_decimal_count)), progress.cost) - sectionTextProgress.visibility = View.VISIBLE + viewBinding.sectionTextProgress.visibility = View.VISIBLE } progress.cost == 0L && data.section.isExam && data.examStatus == ExamStatus.FINISHED -> { - sectionTextProgress.text = context.resources.getString(R.string.section_syllabus_exam_no_score_title) - sectionProgress.progress = 0f - sectionTextProgress.visibility = View.VISIBLE + viewBinding.sectionTextProgress.text = context.resources.getString(R.string.section_syllabus_exam_no_score_title) + viewBinding.sectionProgress.progress = 0f + viewBinding.sectionTextProgress.visibility = View.VISIBLE } else -> { - sectionProgress.progress = 0f - sectionTextProgress.visibility = View.GONE + viewBinding.sectionProgress.progress = 0f + viewBinding.sectionTextProgress.visibility = View.GONE } } } else { - sectionProgress.progress = 0f - sectionTextProgress.visibility = View.GONE + viewBinding.sectionProgress.progress = 0f + viewBinding.sectionTextProgress.visibility = View.GONE } - sectionDownloadStatus.status = sectionDownloadStatuses[data.section.id] ?: DownloadProgress.Status.Pending + viewBinding.sectionDownloadStatus.status = sectionDownloadStatuses[data.section.id] ?: DownloadProgress.Status.Pending sectionTimeLineAdapter.dates = dates - sectionTimeline.isVisible = dates.isNotEmpty() + viewBinding.sectionTimeline.isVisible = dates.isNotEmpty() - sectionDownloadStatus.isVisible = isEnabled && !section.isExam + viewBinding.sectionDownloadStatus.isVisible = isEnabled && !section.isExam val alpha = if (isEnabled) 1f else 0.4f - sectionTitle.alpha = alpha - sectionPosition.alpha = alpha - sectionTimeline.alpha = alpha + viewBinding.sectionTitle.alpha = alpha + viewBinding.sectionPosition.alpha = alpha + viewBinding.sectionTimeline.alpha = alpha if (requiredSection != null) { val requiredPoints = (requiredSection.progress.cost * section.requiredPercent / 100f).roundToInt() - sectionRequirementsDescription.text = + viewBinding.sectionRequirementsDescription.text = context.getString( R.string.course_content_section_requirements, context.resources.getQuantityString(R.plurals.points, requiredPoints.toInt(), requiredPoints), requiredSection.section.title ) - sectionRequirementsDescription.isVisible = true + viewBinding.sectionRequirementsDescription.isVisible = true } else { - sectionRequirementsDescription.isVisible = false + viewBinding.sectionRequirementsDescription.isVisible = false } } } private fun setupExamViews(sectionItem: CourseContentItem.SectionItem) { - sectionExamType.text = if (sectionItem.isProctored) { + viewBinding.sectionExamType.text = if (sectionItem.isProctored) { context.getString(R.string.section_syllabus_exam_chip_proctored_title) } else { context.getString(R.string.section_syllabus_exam_chip_simple_title) } - sectionExamType.background = getColoredDrawable( + viewBinding.sectionExamType.background = getColoredDrawable( R.drawable.bg_shape_rounded_16dp, ContextCompat.getColor(context, R.color.color_overlay_violet_alpha_12) ) @@ -178,13 +170,13 @@ class CourseContentSectionDelegate( R.drawable.ic_clock, ContextCompat.getColor(context, R.color.color_overlay_green) ) - sectionExamStatus.setCompoundDrawablesWithIntrinsicBounds(clockDrawable, null, null, null) - sectionExamStatus.setTextColor(ContextCompat.getColor(context, R.color.color_overlay_green)) - sectionExamStatus.text = sectionItem.section.examDurationMinutes?.let { + viewBinding.sectionExamStatus.setCompoundDrawablesWithIntrinsicBounds(clockDrawable, null, null, null) + viewBinding.sectionExamStatus.setTextColor(ContextCompat.getColor(context, R.color.color_overlay_green)) + viewBinding.sectionExamStatus.text = sectionItem.section.examDurationMinutes?.let { context.resources.getQuantityString(R.plurals.minutes, it, sectionItem.section.examDurationMinutes) } - sectionExamStatus.background = getColoredDrawable( + viewBinding.sectionExamStatus.background = getColoredDrawable( R.drawable.bg_shape_rounded_16dp, ContextCompat.getColor(context, R.color.color_overlay_green_alpha_12) ) @@ -196,12 +188,12 @@ class CourseContentSectionDelegate( evaluationDrawable.addFrame(context.getDrawableCompat(R.drawable.ic_step_quiz_evaluation_frame_3), 250) evaluationDrawable.isOneShot = false DrawableCompat.setTint(evaluationDrawable, ContextCompat.getColor(context, R.color.white)) - sectionExamStatus.setCompoundDrawablesWithIntrinsicBounds(evaluationDrawable, null, null, null) + viewBinding.sectionExamStatus.setCompoundDrawablesWithIntrinsicBounds(evaluationDrawable, null, null, null) evaluationDrawable.start() - sectionExamStatus.setTextColor(ContextCompat.getColor(context, R.color.white)) - sectionExamStatus.text = context.getString(R.string.section_syllabus_exam_in_progress) - sectionExamStatus.background = getColoredDrawable( + viewBinding.sectionExamStatus.setTextColor(ContextCompat.getColor(context, R.color.white)) + viewBinding.sectionExamStatus.text = context.getString(R.string.section_syllabus_exam_in_progress) + viewBinding.sectionExamStatus.background = getColoredDrawable( R.drawable.bg_shape_rounded_16dp, ContextCompat.getColor(context, R.color.color_overlay_violet) ) @@ -211,10 +203,10 @@ class CourseContentSectionDelegate( R.drawable.ic_exam_finished, ContextCompat.getColor(context, R.color.white) ) - sectionExamStatus.setCompoundDrawablesWithIntrinsicBounds(checkDrawable, null, null, null) - sectionExamStatus.setTextColor(ContextCompat.getColor(context, R.color.white)) - sectionExamStatus.text = context.getString(R.string.section_syllabus_exam_finished) - sectionExamStatus.background = getColoredDrawable( + viewBinding.sectionExamStatus.setCompoundDrawablesWithIntrinsicBounds(checkDrawable, null, null, null) + viewBinding.sectionExamStatus.setTextColor(ContextCompat.getColor(context, R.color.white)) + viewBinding.sectionExamStatus.text = context.getString(R.string.section_syllabus_exam_finished) + viewBinding.sectionExamStatus.background = getColoredDrawable( R.drawable.bg_shape_rounded_16dp, ContextCompat.getColor(context, R.color.color_overlay_green) ) @@ -235,8 +227,8 @@ class CourseContentSectionDelegate( null -> "" } - sectionExamAction.text = examActionTitle - sectionExamAction.isVisible = examActionTitle.isNotEmpty() && sectionItem.isEnabled + viewBinding.sectionExamAction.text = examActionTitle + viewBinding.sectionExamAction.isVisible = examActionTitle.isNotEmpty() && sectionItem.isEnabled } private fun getColoredDrawable(@DrawableRes resId: Int, @ColorInt color: Int): Drawable? = @@ -249,4 +241,4 @@ class CourseContentSectionDelegate( DrawableCompat.setTintMode(it, PorterDuff.Mode.SRC_IN) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/stepik/android/view/course_content/ui/adapter/delegates/unit/CourseContentUnitDelegate.kt b/app/src/main/java/org/stepik/android/view/course_content/ui/adapter/delegates/unit/CourseContentUnitDelegate.kt index fb8d99f964..c27c18de7d 100644 --- a/app/src/main/java/org/stepik/android/view/course_content/ui/adapter/delegates/unit/CourseContentUnitDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/course_content/ui/adapter/delegates/unit/CourseContentUnitDelegate.kt @@ -5,9 +5,10 @@ import android.view.ViewGroup import androidx.annotation.DrawableRes import androidx.collection.LongSparseArray import androidx.core.view.isVisible +import by.kirich1409.viewbindingdelegate.viewBinding import com.bumptech.glide.Glide -import kotlinx.android.synthetic.main.view_course_content_unit.view.* import org.stepic.droid.R +import org.stepic.droid.databinding.ViewCourseContentUnitBinding import org.stepic.droid.persistence.model.DownloadProgress import org.stepic.droid.util.toFixed import org.stepik.android.view.course_content.model.CourseContentItem @@ -27,29 +28,16 @@ class CourseContentUnitDelegate( data is CourseContentItem.UnitItem inner class ViewHolder(root: View) : DelegateViewHolder(root) { - private val unitIcon = root.unitIcon - private val unitTitle = root.unitTitle - private val unitDemoAccess = root.unitDemoAccess - private val unitTextProgress = root.unitTextProgress - private val unitProgress = root.unitProgress - - private val unitViewCount = root.unitViewCount - private val unitViewCountIcon = root.unitViewCountIcon - private val unitRating = root.unitRating - private val unitRatingIcon = root.unitRatingIcon - - private val unitTimeToComplete = root.unitTimeToComplete - - private val unitDownloadStatus = root.unitDownloadStatus + private val viewBinding: ViewCourseContentUnitBinding by viewBinding { ViewCourseContentUnitBinding.bind(root) } init { root.setOnClickListener { (itemData as? CourseContentItem.UnitItem)?.let(unitClickListener::onItemClicked) } - unitDownloadStatus.setOnClickListener { + viewBinding.unitDownloadStatus.setOnClickListener { val item = (itemData as? CourseContentItem.UnitItem) ?: return@setOnClickListener - when (unitDownloadStatus.status) { + when (viewBinding.unitDownloadStatus.status) { DownloadProgress.Status.NotCached -> unitClickListener.onItemDownloadClicked(item) @@ -66,7 +54,7 @@ class CourseContentUnitDelegate( override fun onBind(data: CourseContentItem) { with(data as CourseContentItem.UnitItem) { - unitTitle.text = context.resources.getString(R.string.course_content_unit_title, + viewBinding.unitTitle.text = context.resources.getString(R.string.course_content_unit_title, section.position, unit.position, lesson.title) if (progress != null && progress.cost > 0) { val score = progress @@ -74,20 +62,20 @@ class CourseContentUnitDelegate( ?.toFloatOrNull() ?: 0f - unitTextProgress.text = context.resources.getString(R.string.course_content_text_progress_points, + viewBinding.unitTextProgress.text = context.resources.getString(R.string.course_content_text_progress_points, score.toFixed(context.resources.getInteger(R.integer.score_decimal_count)), progress.cost) - unitProgress.progress = score / progress.cost.toFloat() - unitTextProgress.isVisible = true + viewBinding.unitProgress.progress = score / progress.cost.toFloat() + viewBinding.unitTextProgress.isVisible = true } else { - unitProgress.progress = 0f - unitTextProgress.isVisible = false + viewBinding.unitProgress.progress = 0f + viewBinding.unitTextProgress.isVisible = false } val timeToComplete = lesson.timeToComplete.takeIf { it > 60 } ?: lesson.steps.size * 60L if (timeToComplete > 0) { - unitTimeToComplete.isVisible = true + viewBinding.unitTimeToComplete.isVisible = true val timeToCompleteString = if (timeToComplete in 0 until 3600) { val timeValue = timeToComplete / 60 @@ -96,21 +84,21 @@ class CourseContentUnitDelegate( context.resources.getString(R.string.course_content_time_to_complete_hours_unit, timeToComplete / 3600) } - unitTimeToComplete.text = context.getString(R.string.course_content_time_to_complete, timeToCompleteString) + viewBinding.unitTimeToComplete.text = context.getString(R.string.course_content_time_to_complete, timeToCompleteString) } else { - unitTimeToComplete.isVisible = false + viewBinding.unitTimeToComplete.isVisible = false } - unitDownloadStatus.status = unitDownloadStatuses[data.unit.id] ?: DownloadProgress.Status.Pending + viewBinding.unitDownloadStatus.status = unitDownloadStatuses[data.unit.id] ?: DownloadProgress.Status.Pending - Glide.with(unitIcon.context) + Glide.with(viewBinding.unitIcon.context) .asBitmap() .load(lesson.coverUrl) .placeholder(R.drawable.general_placeholder) .centerCrop() - .into(unitIcon) + .into(viewBinding.unitIcon) - unitViewCount.text = lesson.passedBy.toString() + viewBinding.unitViewCount.text = lesson.passedBy.toString() @DrawableRes val unitRatingDrawableRes = @@ -120,22 +108,22 @@ class CourseContentUnitDelegate( R.drawable.ic_course_content_like } - unitRatingIcon.setImageResource(unitRatingDrawableRes) - unitRating.text = abs(lesson.voteDelta).toString() + viewBinding.unitRatingIcon.setImageResource(unitRatingDrawableRes) + viewBinding.unitRating.text = abs(lesson.voteDelta).toString() - unitDownloadStatus.isVisible = access == CourseContentItem.UnitItem.Access.FULL_ACCESS - unitDemoAccess.isVisible = access == CourseContentItem.UnitItem.Access.DEMO + viewBinding.unitDownloadStatus.isVisible = access == CourseContentItem.UnitItem.Access.FULL_ACCESS + viewBinding.unitDemoAccess.isVisible = access == CourseContentItem.UnitItem.Access.DEMO itemView.isEnabled = access != CourseContentItem.UnitItem.Access.NO_ACCESS val alpha = if (access != CourseContentItem.UnitItem.Access.NO_ACCESS) 1f else 0.4f - unitTitle.alpha = alpha - unitRatingIcon.alpha = alpha - unitRating.alpha = alpha - unitViewCount.alpha = alpha - unitViewCountIcon.alpha = alpha - unitTimeToComplete.alpha = alpha - unitTextProgress.alpha = alpha + viewBinding.unitTitle.alpha = alpha + viewBinding.unitRatingIcon.alpha = alpha + viewBinding.unitRating.alpha = alpha + viewBinding.unitViewCount.alpha = alpha + viewBinding.unitViewCountIcon.alpha = alpha + viewBinding.unitTimeToComplete.alpha = alpha + viewBinding.unitTextProgress.alpha = alpha } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/stepik/android/view/course_content/ui/fragment/CourseContentFragment.kt b/app/src/main/java/org/stepik/android/view/course_content/ui/fragment/CourseContentFragment.kt index 0ade6522d1..d8e4df017f 100644 --- a/app/src/main/java/org/stepik/android/view/course_content/ui/fragment/CourseContentFragment.kt +++ b/app/src/main/java/org/stepik/android/view/course_content/ui/fragment/CourseContentFragment.kt @@ -18,19 +18,18 @@ import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import by.kirich1409.viewbindingdelegate.viewBinding import com.google.android.material.snackbar.Snackbar import io.reactivex.disposables.CompositeDisposable import io.reactivex.rxkotlin.Observables.zip import io.reactivex.rxkotlin.plusAssign import io.reactivex.subjects.BehaviorSubject -import kotlinx.android.synthetic.main.empty_default.* -import kotlinx.android.synthetic.main.error_no_connection.* -import kotlinx.android.synthetic.main.fragment_course_content.* import org.stepic.droid.R import org.stepic.droid.analytic.AmplitudeAnalytic import org.stepic.droid.analytic.Analytic import org.stepic.droid.base.App import org.stepic.droid.core.ScreenManager +import org.stepic.droid.databinding.FragmentCourseContentBinding import org.stepic.droid.persistence.model.DownloadProgress import org.stepic.droid.ui.dialogs.LoadingProgressDialogFragment import org.stepic.droid.ui.dialogs.VideoQualityDetailedDialog @@ -100,6 +99,8 @@ class CourseContentFragment : private lateinit var contentAdapter: CourseContentAdapter private var courseId: Long by argument() + private val binding: FragmentCourseContentBinding by viewBinding(FragmentCourseContentBinding::bind) + private val courseContentPresenter: CourseContentPresenter by viewModels { viewModelFactory } private lateinit var viewStateDelegate: ViewStateDelegate @@ -137,7 +138,7 @@ class CourseContentFragment : inflater.inflate(R.layout.fragment_course_content, container, false) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - with(courseContentRecycler) { + with(binding.courseContentRecycler) { contentAdapter = CourseContentAdapter( sectionClickListener = @@ -204,11 +205,11 @@ class CourseContentFragment : } viewStateDelegate = ViewStateDelegate() - viewStateDelegate.addState(courseContentPlaceholder) - viewStateDelegate.addState(courseContentPlaceholder) - viewStateDelegate.addState(courseContentRecycler) - viewStateDelegate.addState(reportProblem) - viewStateDelegate.addState(report_empty) + viewStateDelegate.addState(binding.courseContentPlaceholder) + viewStateDelegate.addState(binding.courseContentPlaceholder) + viewStateDelegate.addState(binding.courseContentRecycler) + viewStateDelegate.addState(binding.errorNoConnection.reportProblem) + viewStateDelegate.addState(binding.emptyDefault.reportEmpty) } override fun onStart() { @@ -292,7 +293,7 @@ class CourseContentFragment : .firstElement() .ignoreElement() .subscribe { - val anchorView = courseContentRecycler.findViewById(R.id.course_control_schedule) + val anchorView = binding.courseContentRecycler.findViewById(R.id.course_control_schedule) val deadlinesDescription = getString(R.string.deadlines_banner_description) PopupHelper.showPopupAnchoredToView(requireContext(), anchorView, deadlinesDescription, cancelableOnTouchOutside = true, withArrow = true) } diff --git a/app/src/main/java/org/stepik/android/view/course_content/ui/view/DownloadStatusView.kt b/app/src/main/java/org/stepik/android/view/course_content/ui/view/DownloadStatusView.kt index f60ca0302d..5d2797ea34 100644 --- a/app/src/main/java/org/stepik/android/view/course_content/ui/view/DownloadStatusView.kt +++ b/app/src/main/java/org/stepik/android/view/course_content/ui/view/DownloadStatusView.kt @@ -2,15 +2,15 @@ package org.stepik.android.view.course_content.ui.view import android.content.Context import android.util.AttributeSet +import android.view.LayoutInflater import android.widget.FrameLayout import android.widget.ProgressBar import android.widget.TextView -import kotlinx.android.synthetic.main.view_download_status.view.* import org.stepic.droid.R +import org.stepic.droid.databinding.ViewDownloadStatusBinding import org.stepic.droid.persistence.model.DownloadProgress import org.stepic.droid.util.TextUtil import org.stepik.android.view.ui.delegate.ViewStateDelegate -import ru.nobird.android.view.base.ui.extension.inflate class DownloadStatusView @JvmOverloads @@ -42,14 +42,14 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 private val viewStateDelegate = ViewStateDelegate() init { - val view = inflate(R.layout.view_download_status, true) - statusCached = view.statusCached + val binding = ViewDownloadStatusBinding.inflate(LayoutInflater.from(context), this) + statusCached = binding.statusCached - viewStateDelegate.addState(view.statusNotCached) + viewStateDelegate.addState(binding.statusNotCached) viewStateDelegate.addState(statusCached) - viewStateDelegate.addState(view.statusPending) - viewStateDelegate.addState(view.statusInProgress) + viewStateDelegate.addState(binding.statusPending) + viewStateDelegate.addState(binding.statusInProgress) - statusProgress = view.statusProgress + statusProgress = binding.statusProgress } -} \ No newline at end of file +} diff --git a/app/src/main/res/layout/empty_default.xml b/app/src/main/res/layout/empty_default.xml index 199f89b524..5aeb0c7140 100644 --- a/app/src/main/res/layout/empty_default.xml +++ b/app/src/main/res/layout/empty_default.xml @@ -8,7 +8,7 @@ android:layout_height="match_parent" android:visibility="gone" tools:visibility="visible" - tools:viewBindingIgnore="true"> +> +> +> \ No newline at end of file +/> \ No newline at end of file diff --git a/app/src/main/res/layout/view_course_content_section.xml b/app/src/main/res/layout/view_course_content_section.xml index fbd85123f4..d879614c4e 100644 --- a/app/src/main/res/layout/view_course_content_section.xml +++ b/app/src/main/res/layout/view_course_content_section.xml @@ -7,7 +7,7 @@ android:layout_height="wrap_content" android:background="@color/color_elevation_overlay_1dp" android:foreground="?selectableItemBackground" - tools:viewBindingIgnore="true"> +> +> +> Date: Wed, 3 Jun 2026 00:33:01 +0200 Subject: [PATCH 16/65] refactor: migrate 5 files from synthetic to viewBinding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PyCharmStepQuizFragment.kt (simple Fragment) - ReviewStatusView.kt (Custom View → binding.inflate) - EditDeadlinesAdapter.kt (ViewHolder → viewBinding delegate) - LearningRateAdapter.kt (ViewHolder → viewBinding delegate) - CodeQuizInstructionDelegate.kt (helper class → XxxBinding.bind) Also removed tools:viewBindingIgnore from 5 layouts. Co-Authored-By: Claude Opus 4.8 --- .../ui/adapters/EditDeadlinesAdapter.kt | 17 ++++++------ .../ui/adapters/LearningRateAdapter.kt | 17 +++++------- .../delegate/CodeQuizInstructionDelegate.kt | 26 +++++++++---------- .../ui/fragment/PyCharmStepQuizFragment.kt | 9 ++++--- .../ui/widget/ReviewStatusView.kt | 12 ++++----- .../res/layout/fragment_step_quiz_pycharm.xml | 2 +- .../main/res/layout/layout_step_quiz_code.xml | 2 +- .../res/layout/view_edit_deadlines_item.xml | 2 +- .../main/res/layout/view_learning_rate.xml | 2 +- .../main/res/layout/view_review_status.xml | 2 +- 10 files changed, 44 insertions(+), 47 deletions(-) diff --git a/app/src/main/java/org/stepik/android/view/personal_deadlines/ui/adapters/EditDeadlinesAdapter.kt b/app/src/main/java/org/stepik/android/view/personal_deadlines/ui/adapters/EditDeadlinesAdapter.kt index 21becdf399..1f5501d442 100644 --- a/app/src/main/java/org/stepik/android/view/personal_deadlines/ui/adapters/EditDeadlinesAdapter.kt +++ b/app/src/main/java/org/stepik/android/view/personal_deadlines/ui/adapters/EditDeadlinesAdapter.kt @@ -2,11 +2,11 @@ package org.stepik.android.view.personal_deadlines.ui.adapters import android.view.View import android.view.ViewGroup -import android.widget.TextView import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView -import kotlinx.android.synthetic.main.view_edit_deadlines_item.view.* +import by.kirich1409.viewbindingdelegate.viewBinding import org.stepic.droid.R +import org.stepic.droid.databinding.ViewEditDeadlinesItemBinding import org.stepic.droid.util.DateTimeHelper import org.stepik.android.domain.personal_deadlines.model.Deadline import org.stepik.android.model.Section @@ -27,16 +27,16 @@ class EditDeadlinesAdapter( EditDeadlinesViewHolder(parent.inflate(R.layout.view_edit_deadlines_item)) override fun onBindViewHolder(holder: EditDeadlinesViewHolder, position: Int) { - holder.sectionTitle.text = holder.itemView.context.getString(R.string.section_title_with_number, + holder.viewBinding.sectionTitle.text = holder.itemView.context.getString(R.string.section_title_with_number, position + 1, sections[position].title) val deadline = getDeadlineForPositionOrNull(position) if (deadline != null) { - holder.deadline.text = holder.itemView.context.getString(R.string.deadlines_section, + holder.viewBinding.deadline.text = holder.itemView.context.getString(R.string.deadlines_section, DateTimeHelper.getPrintableDate(deadline.deadline, DateTimeHelper.DISPLAY_DATETIME_PATTERN, TimeZone.getDefault())) - holder.deadline.isVisible = true + holder.viewBinding.deadline.isVisible = true } else { - holder.deadline.isVisible = false + holder.viewBinding.deadline.isVisible = false } } @@ -66,11 +66,10 @@ class EditDeadlinesAdapter( } inner class EditDeadlinesViewHolder(view: View) : RecyclerView.ViewHolder(view) { - internal val sectionTitle: TextView = view.sectionTitle - internal val deadline: TextView = view.deadline + val viewBinding: ViewEditDeadlinesItemBinding by viewBinding { ViewEditDeadlinesItemBinding.bind(view) } init { view.setOnClickListener { onItemClicked(adapterPosition) } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/stepik/android/view/personal_deadlines/ui/adapters/LearningRateAdapter.kt b/app/src/main/java/org/stepik/android/view/personal_deadlines/ui/adapters/LearningRateAdapter.kt index 5177fea5f1..1c81efb061 100644 --- a/app/src/main/java/org/stepik/android/view/personal_deadlines/ui/adapters/LearningRateAdapter.kt +++ b/app/src/main/java/org/stepik/android/view/personal_deadlines/ui/adapters/LearningRateAdapter.kt @@ -2,11 +2,10 @@ package org.stepik.android.view.personal_deadlines.ui.adapters import android.view.View import android.view.ViewGroup -import android.widget.ImageView -import android.widget.TextView import androidx.recyclerview.widget.RecyclerView -import kotlinx.android.synthetic.main.view_learning_rate.view.* +import by.kirich1409.viewbindingdelegate.viewBinding import org.stepic.droid.R +import org.stepic.droid.databinding.ViewLearningRateBinding import org.stepic.droid.util.AppConstants import org.stepik.android.domain.personal_deadlines.model.LearningRate import ru.nobird.android.view.base.ui.extension.inflate @@ -23,11 +22,11 @@ class LearningRateAdapter( override fun onBindViewHolder(holder: LearningRateViewHolder, position: Int) { val rate = rates[position] - holder.title.setText(rate.title) - holder.icon.setImageResource(rate.icon) + holder.viewBinding.title.setText(rate.title) + holder.viewBinding.icon.setImageResource(rate.icon) val hours = rate.millisPerWeek / AppConstants.MILLIS_IN_1HOUR - holder.rate.text = hours.toString() + holder.viewBinding.rate.text = hours.toString() } private fun onItemClicked(position: Int) { @@ -35,12 +34,10 @@ class LearningRateAdapter( } inner class LearningRateViewHolder(view: View) : RecyclerView.ViewHolder(view) { - internal val title: TextView = view.title - internal val icon: ImageView = view.icon - internal val rate: TextView = view.rate + val viewBinding: ViewLearningRateBinding by viewBinding { ViewLearningRateBinding.bind(view) } init { view.setOnClickListener { onItemClicked(adapterPosition) } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_code/ui/delegate/CodeQuizInstructionDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_code/ui/delegate/CodeQuizInstructionDelegate.kt index 3ffebd986f..0cb3f550b9 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_code/ui/delegate/CodeQuizInstructionDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_code/ui/delegate/CodeQuizInstructionDelegate.kt @@ -5,8 +5,8 @@ import androidx.appcompat.content.res.AppCompatResources import androidx.core.view.isVisible import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager -import kotlinx.android.synthetic.main.layout_step_quiz_code.view.* import org.stepic.droid.R +import org.stepic.droid.databinding.LayoutStepQuizCodeBinding import org.stepic.droid.model.code.ProgrammingLanguage import org.stepic.droid.ui.util.collapse import org.stepic.droid.ui.util.expand @@ -22,9 +22,7 @@ class CodeQuizInstructionDelegate( isCollapseable: Boolean ) { - private val stepQuizCodeDetails = detailsContainerView.stepQuizCodeDetails - private val stepQuizCodeDetailsArrow = detailsContainerView.stepQuizCodeDetailsArrow - private val stepQuizCodeDetailsContent = detailsContainerView.stepQuizCodeDetailsContent + private val binding = LayoutStepQuizCodeBinding.bind(detailsContainerView) private val stepQuizCodeDetailsAdapter = DefaultDelegateAdapter() private val codeStepQuizDetailsMapper = CodeStepQuizDetailsMapper() @@ -33,7 +31,7 @@ class CodeQuizInstructionDelegate( stepQuizCodeDetailsAdapter += CodeDetailSampleAdapterDelegate() stepQuizCodeDetailsAdapter += CodeDetailLimitAdapterDelegate() - with(stepQuizCodeDetailsContent) { + with(binding.stepQuizCodeDetailsContent) { layoutManager = LinearLayoutManager(context) adapter = stepQuizCodeDetailsAdapter isNestedScrollingEnabled = false @@ -44,25 +42,25 @@ class CodeQuizInstructionDelegate( } if (isCollapseable) { - stepQuizCodeDetails.setOnClickListener { - stepQuizCodeDetailsArrow.changeState() - if (stepQuizCodeDetailsArrow.isExpanded()) { - stepQuizCodeDetailsContent.expand() + binding.stepQuizCodeDetails.setOnClickListener { + binding.stepQuizCodeDetailsArrow.changeState() + if (binding.stepQuizCodeDetailsArrow.isExpanded()) { + binding.stepQuizCodeDetailsContent.expand() } else { - stepQuizCodeDetailsContent.collapse() + binding.stepQuizCodeDetailsContent.collapse() } } } else { - stepQuizCodeDetailsContent.isVisible = true + binding.stepQuizCodeDetailsContent.isVisible = true } } fun setCodeDetailsData(step: Step, lang: String?) { if (lang == ProgrammingLanguage.SQL.serverPrintableName) { - stepQuizCodeDetails.isVisible = false + binding.stepQuizCodeDetails.isVisible = false } else { stepQuizCodeDetailsAdapter.items = codeStepQuizDetailsMapper.mapToCodeDetails(step, lang) - stepQuizCodeDetails.isVisible = stepQuizCodeDetailsAdapter.items.isNotEmpty() + binding.stepQuizCodeDetails.isVisible = stepQuizCodeDetailsAdapter.items.isNotEmpty() } } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_pycharm/ui/fragment/PyCharmStepQuizFragment.kt b/app/src/main/java/org/stepik/android/view/step_quiz_pycharm/ui/fragment/PyCharmStepQuizFragment.kt index 146e930a1a..f32b947b3b 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_pycharm/ui/fragment/PyCharmStepQuizFragment.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_pycharm/ui/fragment/PyCharmStepQuizFragment.kt @@ -6,8 +6,9 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment -import kotlinx.android.synthetic.main.fragment_step_quiz_pycharm.* +import by.kirich1409.viewbindingdelegate.viewBinding import org.stepic.droid.R +import org.stepic.droid.databinding.FragmentStepQuizPycharmBinding class PyCharmStepQuizFragment : Fragment() { companion object { @@ -15,12 +16,14 @@ class PyCharmStepQuizFragment : Fragment() { PyCharmStepQuizFragment() } + private val binding: FragmentStepQuizPycharmBinding by viewBinding(FragmentStepQuizPycharmBinding::bind) + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.fragment_step_quiz_pycharm, container, false) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - stepQuizFeedback.movementMethod = LinkMovementMethod.getInstance() + binding.stepQuizFeedback.movementMethod = LinkMovementMethod.getInstance() } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_review/ui/widget/ReviewStatusView.kt b/app/src/main/java/org/stepik/android/view/step_quiz_review/ui/widget/ReviewStatusView.kt index 2cd9e2e7e5..2e5b22d35d 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_review/ui/widget/ReviewStatusView.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_review/ui/widget/ReviewStatusView.kt @@ -3,15 +3,15 @@ package org.stepik.android.view.step_quiz_review.ui.widget import android.content.Context import android.content.res.ColorStateList import android.util.AttributeSet +import android.view.LayoutInflater import android.view.View import android.widget.FrameLayout import android.widget.TextView import androidx.appcompat.content.res.AppCompatResources import androidx.core.view.isVisible -import kotlinx.android.synthetic.main.view_review_status.view.* import org.stepic.droid.R +import org.stepic.droid.databinding.ViewReviewStatusBinding import org.stepic.droid.util.resolveColorAttribute -import ru.nobird.android.view.base.ui.extension.inflate class ReviewStatusView @JvmOverloads @@ -55,10 +55,10 @@ constructor( } init { - val view = inflate(R.layout.view_review_status, true) + val binding = ViewReviewStatusBinding.inflate(LayoutInflater.from(context), this) - textView = view.peerReviewStatusText - imageView = view.peerReviewStatusImage + textView = binding.peerReviewStatusText + imageView = binding.peerReviewStatusImage val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ReviewStatusView) try { @@ -71,4 +71,4 @@ constructor( enum class Status { ERROR, PENDING, IN_PROGRESS, COMPLETED } -} \ No newline at end of file +} diff --git a/app/src/main/res/layout/fragment_step_quiz_pycharm.xml b/app/src/main/res/layout/fragment_step_quiz_pycharm.xml index 90bc128aea..66a47659fb 100644 --- a/app/src/main/res/layout/fragment_step_quiz_pycharm.xml +++ b/app/src/main/res/layout/fragment_step_quiz_pycharm.xml @@ -7,7 +7,7 @@ android:layout_height="match_parent" android:orientation="vertical" android:gravity="bottom" - tools:viewBindingIgnore="true"> +> +> +> +> +> Date: Wed, 3 Jun 2026 01:02:40 +0200 Subject: [PATCH 17/65] refactor: migrate 5 files from synthetic to viewBinding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migrated: - CodeStepQuizFormDelegate (delegate→LayoutStepQuizCodeBinding) - CodeStepRunCodeDelegate (delegate→LayoutStepQuizCodeFullscreenRunCodeBinding) - StepQuizReviewDelegate (LayoutContainer→findViewById) - StepQuizReviewTeacherFragment (fragment→viewBinding delegate) - StepQuizReviewFragment (fragment→findViewById) Removed tools:viewBindingIgnore from 6 layout files: - fragment_step_quiz_review.xml - fragment_step_quiz_review_peer.xml - fragment_step_quiz_review_teacher.xml - layout_step_quiz_code_fullscreen_run_code.xml - layout_step_quiz_review_header.xml - layout_step_quiz_review_footer.xml Co-Authored-By: Claude Opus 4.8 --- .../ui/delegate/CodeStepQuizFormDelegate.kt | 18 ++--- .../ui/delegate/CodeStepRunCodeDelegate.kt | 26 ++++--- .../ui/delegate/StepQuizReviewDelegate.kt | 61 +++++++++++++--- .../ui/fragment/StepQuizReviewFragment.kt | 22 ++++-- .../fragment/StepQuizReviewTeacherFragment.kt | 73 ++++++++++--------- .../res/layout/fragment_step_quiz_review.xml | 2 +- .../layout/fragment_step_quiz_review_peer.xml | 2 +- .../fragment_step_quiz_review_teacher.xml | 2 +- ...out_step_quiz_code_fullscreen_run_code.xml | 2 +- .../layout/layout_step_quiz_review_footer.xml | 1 - .../layout/layout_step_quiz_review_header.xml | 1 - 11 files changed, 131 insertions(+), 79 deletions(-) diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_code/ui/delegate/CodeStepQuizFormDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_code/ui/delegate/CodeStepQuizFormDelegate.kt index d1d4b26e7e..f1f7c1413c 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_code/ui/delegate/CodeStepQuizFormDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_code/ui/delegate/CodeStepQuizFormDelegate.kt @@ -3,10 +3,8 @@ package org.stepik.android.view.step_quiz_code.ui.delegate import android.view.View import androidx.core.widget.doAfterTextChanged import androidx.recyclerview.widget.LinearLayoutManager -import kotlinx.android.synthetic.main.layout_step_quiz_code.view.* -import kotlinx.android.synthetic.main.layout_step_quiz_code_fullscreen_playground.view.codeStepLayout -import kotlinx.android.synthetic.main.layout_step_quiz_code_fullscreen_playground.view.stepQuizActions import org.stepic.droid.R +import org.stepic.droid.databinding.LayoutStepQuizCodeBinding import org.stepic.droid.ui.util.setCompoundDrawables import org.stepik.android.model.Reply import org.stepik.android.model.code.CodeOptions @@ -28,6 +26,8 @@ class CodeStepQuizFormDelegate( private val syncCodePreference: (String) -> Unit, private val onQuizChanged: (ReplyResult) -> Unit ) : StepQuizFormDelegate { + private val binding = LayoutStepQuizCodeBinding.bind(containerView) + private var state: CodeStepQuizFormState = CodeStepQuizFormState.Idle set(value) { field = value @@ -44,11 +44,11 @@ class CodeStepQuizFormDelegate( private val viewStateDelegate = ViewStateDelegate() - private val codeLayout = containerView.codeStepLayout - private val stepQuizActions = containerView.stepQuizActions + private val codeLayout = binding.codeStepLayout + private val stepQuizActions = binding.stepQuizActions - private val stepQuizCodeLangChooserTitle = containerView.stepQuizCodeLangChooserTitle - private val stepQuizCodeLangChooser = containerView.stepQuizCodeLangChooser + private val stepQuizCodeLangChooserTitle = binding.stepQuizCodeLangChooserTitle + private val stepQuizCodeLangChooser = binding.stepQuizCodeLangChooser private val stepQuizCodeLangChooserAdapter = DefaultDelegateAdapter() private val codeStepQuizFormStateMapper = CodeStepQuizFormStateMapper() @@ -56,7 +56,7 @@ class CodeStepQuizFormDelegate( init { viewStateDelegate.addState() viewStateDelegate.addState(stepQuizCodeLangChooserTitle, stepQuizCodeLangChooser, - containerView.stepQuizCodeLangChooserDividerTop, containerView.stepQuizCodeLangChooserDividerBottom) + binding.stepQuizCodeLangChooserDividerTop.root, binding.stepQuizCodeLangChooserDividerBottom.root) viewStateDelegate.addState(codeLayout, stepQuizActions) /** @@ -114,4 +114,4 @@ class CodeStepQuizFormDelegate( fun updateCodeLayoutFromDialog(lang: String, code: String) { state = CodeStepQuizFormState.Lang(lang, code) } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_code/ui/delegate/CodeStepRunCodeDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_code/ui/delegate/CodeStepRunCodeDelegate.kt index efee122f9c..e166b37e5d 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_code/ui/delegate/CodeStepRunCodeDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_code/ui/delegate/CodeStepRunCodeDelegate.kt @@ -11,8 +11,8 @@ import androidx.appcompat.widget.ListPopupWindow import androidx.core.graphics.drawable.DrawableCompat import androidx.core.view.isGone import com.google.android.material.tabs.TabLayout -import kotlinx.android.synthetic.main.layout_step_quiz_code_fullscreen_run_code.view.* import org.stepic.droid.R +import org.stepic.droid.databinding.LayoutStepQuizCodeFullscreenRunCodeBinding import org.stepic.droid.code.ui.CodeEditorLayout import org.stepic.droid.model.code.ProgrammingLanguage import org.stepic.droid.persistence.model.StepPersistentWrapper @@ -39,16 +39,18 @@ class CodeStepRunCodeDelegate( private const val RUN_CODE_TAB = 2 } - private val runCodeScrollView = runCodeLayout.dataScrollView - private val runCodeInputDataTitle = runCodeLayout.inputDataTitle - private val runCodeInputSamplePicker = runCodeLayout.inputDataSamplePicker - private val runCodeInputDataSample = runCodeLayout.inputDataSample - private val runCodeOutputDataSeparator = runCodeLayout.outputSeparator - private val runCodeOutputDataTitle = runCodeLayout.outputDataTitle - private val runCodeOutputDataSample = runCodeLayout.outputDataSample - private val runCodeFeedback = runCodeLayout.runCodeFeedback - private val runCodeFab = runCodeLayout.runCodeFab - private val runCodeAction = runCodeLayout.runCodeAction + private val binding = LayoutStepQuizCodeFullscreenRunCodeBinding.bind(runCodeLayout) + + private val runCodeScrollView = binding.dataScrollView + private val runCodeInputDataTitle = binding.inputDataTitle + private val runCodeInputSamplePicker = binding.inputDataSamplePicker + private val runCodeInputDataSample = binding.inputDataSample + private val runCodeOutputDataSeparator = binding.outputSeparator.root + private val runCodeOutputDataTitle = binding.outputDataTitle + private val runCodeOutputDataSample = binding.outputDataSample + private val runCodeFeedback = binding.runCodeFeedback + private val runCodeFab = binding.runCodeFab + private val runCodeAction = binding.runCodeAction var lang: String = "" set(value) { @@ -235,4 +237,4 @@ class CodeStepRunCodeDelegate( stepId = stepWrapper.step.id ) } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/stepik/android/view/step_quiz_review/ui/delegate/StepQuizReviewDelegate.kt b/app/src/main/java/org/stepik/android/view/step_quiz_review/ui/delegate/StepQuizReviewDelegate.kt index a49f9182ca..607ce91c95 100644 --- a/app/src/main/java/org/stepik/android/view/step_quiz_review/ui/delegate/StepQuizReviewDelegate.kt +++ b/app/src/main/java/org/stepik/android/view/step_quiz_review/ui/delegate/StepQuizReviewDelegate.kt @@ -2,14 +2,11 @@ package org.stepik.android.view.step_quiz_review.ui.delegate import android.view.View import android.view.ViewGroup +import android.widget.Button +import android.widget.TextView import androidx.annotation.PluralsRes import androidx.annotation.StringRes import androidx.core.view.isVisible -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.error_no_connection_with_button_small.view.* -import kotlinx.android.synthetic.main.fragment_step_quiz_review_peer.* -import kotlinx.android.synthetic.main.layout_step_quiz_review_footer.* -import kotlinx.android.synthetic.main.layout_step_quiz_review_header.* import org.stepic.droid.R import org.stepik.android.model.ReviewStrategyType import org.stepik.android.model.Submission @@ -24,7 +21,7 @@ import org.stepik.android.view.ui.delegate.ViewStateDelegate import ru.nobird.app.core.model.safeCast class StepQuizReviewDelegate( - override val containerView: View, + containerView: View, private val instructionType: ReviewStrategyType, private val actionListener: ActionListener, @@ -32,10 +29,52 @@ class StepQuizReviewDelegate( private val quizView: View, private val quizDelegate: StepQuizDelegate, private val quizFeedbackBlocksDelegate: StepQuizFeedbackBlocksDelegate -) : LayoutContainer { - private val stepQuizFeedbackMapper = StepQuizFeedbackMapper() +) { private val resources = containerView.resources + private val stepQuizNetworkError = containerView.findViewById(R.id.stepQuizNetworkError) + private val stepQuizProgress = containerView.findViewById(R.id.stepQuizProgress) + private val stepQuizDescription = containerView.findViewById(R.id.stepQuizDescription) + private val quizFeedbackView = containerView.findViewById(R.id.quizFeedbackView) + + private val reviewStep1DividerBottom = containerView.findViewById(R.id.reviewStep1DividerBottom) + private val reviewStep1Container = containerView.findViewById(R.id.reviewStep1Container) + private val reviewStep1Discounting = containerView.findViewById(R.id.reviewStep1Discounting) + private val reviewStep1QuizContainer = containerView.findViewById(R.id.reviewStep1QuizContainer) + private val reviewStep1ActionButton = containerView.findViewById(R.id.reviewStep1ActionButton) + private val reviewStep1ActionRetry = containerView.findViewById(R.id.reviewStep1ActionRetry) + private val reviewStep1Status = containerView.findViewById(R.id.reviewStep1Status) + + private val reviewStep2DividerBottom = containerView.findViewById(R.id.reviewStep2DividerBottom) + private val reviewStep2Container = containerView.findViewById(R.id.reviewStep2Container) + private val reviewStep2Loading = containerView.findViewById(R.id.reviewStep2Loading) + private val reviewStep2CreateSession = containerView.findViewById(R.id.reviewStep2CreateSession) + private val reviewStep2SelectSubmission = containerView.findViewById(R.id.reviewStep2SelectSubmission) + private val reviewStep2Retry = containerView.findViewById(R.id.reviewStep2Retry) + private val reviewStep2Title = containerView.findViewById(R.id.reviewStep2Title) + private val reviewStep2Link = containerView.findViewById(R.id.reviewStep2Link) + private val reviewStep2Status = containerView.findViewById(R.id.reviewStep2Status) + + private val reviewStep3Title = containerView.findViewById(R.id.reviewStep3Title) + private val reviewStep3Link = containerView.findViewById(R.id.reviewStep3Link) + private val reviewStep3Status = containerView.findViewById(R.id.reviewStep3Status) + private val reviewStep3Container = containerView.findViewById