diff --git a/.github/workflows/codeball.yml b/.github/workflows/codeball.yml new file mode 100644 index 0000000..b2f1f0f --- /dev/null +++ b/.github/workflows/codeball.yml @@ -0,0 +1,21 @@ +name: Codeball +on: + pull_request: {} + pull_request_review_comment: + types: [created, edited] + +jobs: + codeball_job: + runs-on: ubuntu-latest + name: Codeball + steps: + # Run Codeball on all new Pull Requests and Review Comments! 🚀 + # For customizations and more documentation, see https://github.com/sturdy-dev/codeball-action + - name: Codeball + uses: sturdy-dev/codeball-action@v2 + with: + approvePullRequests: "true" + labelPullRequestsWhenApproved: "true" + labelPullRequestsWhenReviewNeeded: "false" + failJobsWhenReviewNeeded: "false" + codeSuggestionsFromComments: "true" diff --git a/.idea/compiler.xml b/.idea/compiler.xml index fb7f4a8..b589d56 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml index a2d7c21..11c2963 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -4,15 +4,16 @@ diff --git a/.idea/misc.xml b/.idea/misc.xml index da4611c..510cd47 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - - + diff --git a/app/build.gradle b/app/build.gradle index 3c20376..9d141cf 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,23 +7,27 @@ plugins { } android { - compileSdk 32 + compileSdk 33 defaultConfig { applicationId "com.example.associate.training" minSdk 21 - targetSdk 32 + targetSdk 33 versionCode 1 versionName "1.0" // For androidx.renderscript. renderscriptTargetApi 11 renderscriptSupportModeEnabled true - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + testInstrumentationRunner "com.example.associate.training.InstrumentationTestRunner" } buildFeatures { viewBinding true dataBinding true + compose true + } + composeOptions { + kotlinCompilerExtensionVersion = "1.4.2" } buildTypes { release { @@ -36,58 +40,75 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = '18' } + namespace 'com.example.associate.training' } dependencies { - // ViewModel - implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" - implementation "androidx.activity:activity-ktx:1.4.0" - // WorkInfo, WorkManager - implementation "androidx.work:work-runtime-ktx:2.7.1" +// AndroidX Libraries + implementation "androidx.core:core-ktx:1.7.0" + implementation "androidx.appcompat:appcompat:1.4.1" + implementation "com.google.android.material:material:1.6.0" + implementation "androidx.constraintlayout:constraintlayout:2.1.3" + // Compose + def compose_ui_version = '1.4.0' + implementation "androidx.compose.ui:ui:$compose_ui_version" + implementation "androidx.compose.ui:ui-tooling-preview:$compose_ui_version" + implementation "androidx.compose.ui:ui-tooling:$compose_ui_version" + implementation 'androidx.activity:activity-compose:1.7.0' + implementation 'androidx.compose.material:material:1.4.0' - // androidx.navigation, NavController + // Navigation + def nav_version = "2.4.2" implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" implementation "androidx.navigation:navigation-ui-ktx:$nav_version" + androidTestImplementation "androidx.navigation:navigation-testing:$nav_version" + + // ViewModel and Lifecycle + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + implementation "androidx.activity:activity-ktx:1.4.0" + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" - // Room libraries + // + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4" + + // WorkManager + implementation "androidx.work:work-runtime-ktx:2.7.1" + + // Room and DataStore + def room_version = "2.5.1" implementation "androidx.room:room-runtime:$room_version" kapt "androidx.room:room-compiler:$room_version" - - // optional - Kotlin Extensions and Coroutines support for Room implementation "androidx.room:room-ktx:$room_version" - // Image Loader + implementation "androidx.datastore:datastore-preferences:1.0.0" + + // Image Loading implementation "io.coil-kt:coil:2.1.0" - // Moshi + // JSON Parsing + def moshi_version = "1.12.0" implementation "com.squareup.moshi:moshi-kotlin:$moshi_version" - // Retrofit with Moshi Converter - implementation "com.squareup.retrofit2:converter-moshi:$retrofit_version" - - implementation 'androidx.core:core-ktx:1.7.0' - implementation 'androidx.appcompat:appcompat:1.4.1' - implementation 'com.google.android.material:material:1.6.0' - implementation 'androidx.constraintlayout:constraintlayout:2.1.3' - // Gson - implementation 'com.google.code.gson:gson:2.8.9' - - // DataStore - implementation "androidx.datastore:datastore-preferences:1.0.0" - // LiveData - implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" - - // Testing-only dependencies - testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.navigation:navigation-testing:2.4.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.3' - androidTestImplementation "androidx.test:core:$test_core_version" - androidTestImplementation "androidx.test:runner:$test_core_version" - androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_version" - androidTestImplementation "androidx.test.espresso:espresso-contrib:$espresso_version" + implementation "com.squareup.retrofit2:converter-moshi:2.9.0" + implementation "com.google.code.gson:gson:2.8.9" - androidTestImplementation 'androidx.navigation:navigation-testing:2.4.2' + // Dependency Injection + def koin_version = "3.1.2" + implementation "io.insert-koin:koin-android:$koin_version" + testImplementation "io.insert-koin:koin-test:$koin_version" + androidTestImplementation "io.insert-koin:koin-test:$koin_version" + testImplementation "io.insert-koin:koin-test-junit4:$koin_version" - debugImplementation 'androidx.fragment:fragment-testing:1.4.1' + // glide + implementation 'com.github.bumptech.glide:glide:4.13.2' + // Testing Dependencies + def junit_version = "4.13.2" + testImplementation "junit:junit:$junit_version" + androidTestImplementation "androidx.test.ext:junit:1.1.3" + androidTestImplementation "androidx.test:core:1.4.0" + androidTestImplementation "androidx.test:runner:1.4.0" + androidTestImplementation "androidx.test.espresso:espresso-core:3.4.0" + androidTestImplementation "androidx.test.espresso:espresso-contrib:3.4.0" + debugImplementation "androidx.fragment:fragment-testing:1.4.1" } \ No newline at end of file diff --git a/app/src/androidTest/java/com/example/associate/training/MediaActivityTest.kt b/app/src/androidTest/java/com/example/associate/training/MediaActivityTest.kt new file mode 100644 index 0000000..15da223 --- /dev/null +++ b/app/src/androidTest/java/com/example/associate/training/MediaActivityTest.kt @@ -0,0 +1,60 @@ +package com.example.associate.training + +import android.view.View +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.* +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.example.associate.training.media.MediaActivity +import com.google.android.material.progressindicator.CircularProgressIndicator +import org.hamcrest.Description +import org.hamcrest.Matchers.allOf +import org.hamcrest.TypeSafeMatcher +import org.junit.Assert.* +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class MediaActivityTest { + + @get:Rule + val activity = ActivityScenarioRule(MediaActivity::class.java) + + @Test + fun testEncoding() { + + val startTime = System.currentTimeMillis() + var clicked = false + val progressIndicator = onView(withId(R.id.progress_indicator)) + val button = onView(withId(R.id.export_to_video)) + while (!clicked && System.currentTimeMillis() - startTime < 10000) { + try { + button.check(matches(allOf(isClickable(), isDisplayed()))) + button.perform(click()) + // We need to wait for the progress indicator to be displayed because sometimes performing click is not working + progressIndicator.check(matches(object : TypeSafeMatcher() { + override fun describeTo(description: Description?) { + description?.appendText("Indeterminate progress bar") + } + + override fun matchesSafely(item: View?): Boolean { + return item is CircularProgressIndicator && item.isIndeterminate + } + })) + clicked = true + } catch (e: Throwable) { + Thread.sleep(100) + } + } + + Thread.sleep(10000) // Wait for encoding to complete (adjust time as needed) + + // Verify that the "Complete" Toast is displayed + onView(withId(R.id.content)).check(matches(withText("Complete"))) + + } + +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/example/associate/training/ParcelableTest.kt b/app/src/androidTest/java/com/example/associate/training/ParcelableTest.kt new file mode 100644 index 0000000..bfc0368 --- /dev/null +++ b/app/src/androidTest/java/com/example/associate/training/ParcelableTest.kt @@ -0,0 +1,95 @@ +package com.example.associate.training + +import android.os.Parcel +import android.os.Parcelable +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Assert.* +import org.junit.Test +import org.junit.runner.RunWith + +/** + * To have a custom model object which implements the interface Parcelable in order to pass objects between components + * (activities, fragments, services, etc…) + * See [medium](https://medium.com/@dbottillo/android-junit-parcelable-model-test-37a2f2ae18b1). + */ +@RunWith(AndroidJUnit4::class) +class ParcelableTest { + + @Test + fun object_equal() { + val book = Book("author", "title") + + val parcel = Parcel.obtain() + book.writeToParcel(parcel, book.describeContents()) + parcel.setDataPosition(0) + + val createdFromParcel = Book.createFromParcel(parcel) + assertEquals("author", createdFromParcel.author) + assertEquals("title", createdFromParcel.title) + } + + @Test + fun test_subclass() { + val book = SubBook("author", "title", true) + + val parcel = Parcel.obtain() + book.writeToParcel(parcel, book.describeContents()) + parcel.setDataPosition(0) + + val createdFromParcel = SubBook.createFromParcel(parcel) + assertEquals("author", createdFromParcel.author) + assertEquals("title", createdFromParcel.title) + assertEquals(true, createdFromParcel.isAdult) + } +} + +open class Book : Parcelable { + val author: String + val title: String + + constructor(author: String, title: String) { + this.author = author + this.title = title + } + + constructor(`in`: Parcel) { + author = `in`.readString().toString() + title = `in`.readString().toString() + } + + override fun describeContents(): Int { + return 0 + } + + override fun writeToParcel(dest: Parcel, flags: Int) { + dest.writeString(author) + dest.writeString(title) + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel) = Book(parcel) + override fun newArray(size: Int): Array = arrayOfNulls(size) + } +} + +class SubBook : Book { + var isAdult: Boolean = false + + constructor(author: String, title: String, isAdult: Boolean) : super(author, title) { + this.isAdult = isAdult + } + + constructor(`in`: Parcel) : super(`in`) { + isAdult = `in`.readBoolean() + } + + override fun writeToParcel(dest: Parcel, flags: Int) { + super.writeToParcel(dest, flags) + dest.writeBoolean(isAdult) + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel) = SubBook(parcel) + override fun newArray(size: Int): Array = arrayOfNulls(size) + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/example/associate/training/core/InstrumentationTestRunner.kt b/app/src/androidTest/java/com/example/associate/training/core/InstrumentationTestRunner.kt new file mode 100644 index 0000000..5d40f19 --- /dev/null +++ b/app/src/androidTest/java/com/example/associate/training/core/InstrumentationTestRunner.kt @@ -0,0 +1,15 @@ +package com.example.associate.training.core + +import android.app.Application +import android.content.Context +import androidx.test.runner.AndroidJUnitRunner + +class InstrumentationTestRunner : AndroidJUnitRunner() { + override fun newApplication( + classLoader: ClassLoader?, + className: String?, + context: Context? + ): Application { + return super.newApplication(classLoader, TestApplication::class.java.name, context) + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/example/associate/training/core/TestApplication.kt b/app/src/androidTest/java/com/example/associate/training/core/TestApplication.kt new file mode 100644 index 0000000..a382cb5 --- /dev/null +++ b/app/src/androidTest/java/com/example/associate/training/core/TestApplication.kt @@ -0,0 +1,22 @@ +package com.example.associate.training.core + +import android.app.Application + +import com.example.associate.training.media.MediaViewModel +import org.koin.core.context.startKoin +import org.koin.dsl.module + +class TestApplication: Application() { + override fun onCreate() { + super.onCreate() + val testModule = module { + factory { + MediaViewModel(height = 480, width = 720) + } + } + startKoin { + //androidContext(ApplicationProvider.getApplicationContext()) + modules(testModule) + } + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f3b4f6b..19bddc1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,12 +1,12 @@ - + + + @@ -82,6 +85,23 @@ android:name=".pic.PicActivity" android:exported="true"/> + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/example/associate/training/AssociateApplication.kt b/app/src/main/java/com/example/associate/training/AssociateApplication.kt new file mode 100644 index 0000000..1f9d8f3 --- /dev/null +++ b/app/src/main/java/com/example/associate/training/AssociateApplication.kt @@ -0,0 +1,38 @@ +package com.example.associate.training + +import android.app.Application +import com.example.associate.training.busschedule.database.AppDatabase +import com.example.associate.training.inventory.data.ItemRoomDatabase +import com.example.associate.training.koin.HelloRepository +import com.example.associate.training.koin.HelloRepositoryImpl +import com.example.associate.training.koin.MyViewModel +import com.example.associate.training.media.MediaViewModel +import org.koin.android.ext.koin.androidContext +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.core.context.startKoin +import org.koin.dsl.module + +val appModule = module { + + // single instance of HelloRepository + single { HelloRepositoryImpl() } + + // MyViewModel ViewModel + viewModel { MyViewModel(get()) } + + // Define the MediaViewModel as a Koin dependency + factory { (height: Int, width: Int) -> MediaViewModel(height, width) } +} + +class AssociateApplication : Application() { + val database: AppDatabase by lazy { AppDatabase.getDatabase(this) } + val inventoryDatabase: ItemRoomDatabase by lazy { ItemRoomDatabase.getDatabase(this) } + override fun onCreate() { + super.onCreate() + // Start Koin + startKoin{ + androidContext(this@AssociateApplication) + modules(appModule) + } + } +} diff --git a/app/src/main/java/com/example/associate/training/MainActivity.kt b/app/src/main/java/com/example/associate/training/MainActivity.kt index 6aca687..f86ec63 100644 --- a/app/src/main/java/com/example/associate/training/MainActivity.kt +++ b/app/src/main/java/com/example/associate/training/MainActivity.kt @@ -4,14 +4,19 @@ import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import androidx.recyclerview.widget.LinearLayoutManager import com.example.associate.training.amphibian.AmphibianActivity +import com.example.associate.training.media.MediaActivity import com.example.associate.training.async.ThreadActivity import com.example.associate.training.busschedule.BusScheduleActivity import com.example.associate.training.canvas.CanvasActivity +import com.example.associate.training.compose.ComposeActivity import com.example.associate.training.databinding.ActivityMainBinding import com.example.associate.training.dummy.DummyActivity import com.example.associate.training.inventory.InventoryActivity +import com.example.associate.training.koin.KoinActivity import com.example.associate.training.lifecycle.LifecycleActivity +import com.example.associate.training.misc.MiscActivity import com.example.associate.training.pic.PicActivity +import com.example.associate.training.views.ViewsActivity import com.example.associate.training.word.WordActivity import com.example.associate.training.workmanager.WorkManagerActivity @@ -34,8 +39,13 @@ open class MainActivity : AppCompatActivity() { open fun getList(): List { return listOf( + Entry("ComposeActivity", ComposeActivity::class.java), + Entry("MediaActivity", MediaActivity::class.java), + Entry("KoinActivity", KoinActivity::class.java), + Entry("ViewsActivity", ViewsActivity::class.java), + Entry("MiscActivity", MiscActivity::class.java), Entry("CanvasActivity", CanvasActivity::class.java), - Entry("ViewsActivity", PicActivity::class.java), + Entry("PicActivity", PicActivity::class.java), Entry("WorkManager", WorkManagerActivity::class.java), Entry("ROOM Database \n sql", BusScheduleActivity::class.java), Entry("Amphibian \n Retrofit, Moshi, DataBinding", AmphibianActivity::class.java), diff --git a/app/src/main/java/com/example/associate/training/busschedule/BusScheduleApplication.kt b/app/src/main/java/com/example/associate/training/busschedule/BusScheduleApplication.kt deleted file mode 100644 index ae01c31..0000000 --- a/app/src/main/java/com/example/associate/training/busschedule/BusScheduleApplication.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.example.associate.training.busschedule -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import android.app.Application -import com.example.associate.training.busschedule.database.AppDatabase -import com.example.associate.training.inventory.data.ItemRoomDatabase - -class BusScheduleApplication : Application() { - val database: AppDatabase by lazy { AppDatabase.getDatabase(this) } - val inventoryDatabase: ItemRoomDatabase by lazy { ItemRoomDatabase.getDatabase(this) } -} diff --git a/app/src/main/java/com/example/associate/training/busschedule/FullScheduleFragment.kt b/app/src/main/java/com/example/associate/training/busschedule/FullScheduleFragment.kt index 98a92e8..1e36513 100644 --- a/app/src/main/java/com/example/associate/training/busschedule/FullScheduleFragment.kt +++ b/app/src/main/java/com/example/associate/training/busschedule/FullScheduleFragment.kt @@ -1,18 +1,4 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + package com.example.associate.training.busschedule import android.os.Bundle @@ -25,6 +11,7 @@ import androidx.lifecycle.coroutineScope import androidx.navigation.findNavController import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import com.example.associate.training.AssociateApplication import com.example.associate.training.busschedule.viewmodels.BusScheduleViewModel import com.example.associate.training.busschedule.viewmodels.BusScheduleViewModelFactory import com.example.associate.training.databinding.FragmentFullScheduleBinding @@ -40,7 +27,7 @@ class FullScheduleFragment: Fragment() { private val viewModel: BusScheduleViewModel by activityViewModels { BusScheduleViewModelFactory( - (activity?.application as BusScheduleApplication).database.scheduleDao() + (activity?.application as AssociateApplication).database.scheduleDao() ) } @@ -48,7 +35,7 @@ class FullScheduleFragment: Fragment() { inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { + ): View { _binding = FragmentFullScheduleBinding.inflate(inflater, container, false) val view = binding.root return view diff --git a/app/src/main/java/com/example/associate/training/busschedule/StopScheduleFragment.kt b/app/src/main/java/com/example/associate/training/busschedule/StopScheduleFragment.kt index a10c12a..97389f0 100644 --- a/app/src/main/java/com/example/associate/training/busschedule/StopScheduleFragment.kt +++ b/app/src/main/java/com/example/associate/training/busschedule/StopScheduleFragment.kt @@ -1,18 +1,3 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ package com.example.associate.training.busschedule import android.os.Bundle @@ -24,6 +9,7 @@ import androidx.fragment.app.activityViewModels import androidx.lifecycle.coroutineScope import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import com.example.associate.training.AssociateApplication import com.example.associate.training.busschedule.viewmodels.BusScheduleViewModel import com.example.associate.training.busschedule.viewmodels.BusScheduleViewModelFactory import com.example.associate.training.databinding.FragmentStopScheduleBinding @@ -45,7 +31,7 @@ class StopScheduleFragment: Fragment() { private val viewModel: BusScheduleViewModel by activityViewModels { BusScheduleViewModelFactory( - (activity?.application as BusScheduleApplication).database.scheduleDao() + (activity?.application as AssociateApplication).database.scheduleDao() ) } diff --git a/app/src/main/java/com/example/associate/training/canvas/view/CanvasListFragment.kt b/app/src/main/java/com/example/associate/training/canvas/view/CanvasListFragment.kt index ec78c3c..c871302 100644 --- a/app/src/main/java/com/example/associate/training/canvas/view/CanvasListFragment.kt +++ b/app/src/main/java/com/example/associate/training/canvas/view/CanvasListFragment.kt @@ -18,7 +18,7 @@ import com.example.associate.training.databinding.ActivityMainBinding class CanvasListFragment : Fragment() { - private lateinit var adapter: TestAdapter + private lateinit var adapter: CanvasListAdapter private lateinit var binding: ActivityMainBinding override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -36,7 +36,7 @@ class CanvasListFragment : Fragment() { } private fun setupAdapter() { - adapter = TestAdapter(CanvasViewType.values().toList(), activity as AppCompatActivity) + adapter = CanvasListAdapter(CanvasViewType.values().toList(), activity as AppCompatActivity) binding.tutorsRecyclerView.layoutManager = LinearLayoutManager(binding.root.context) binding.tutorsRecyclerView.adapter = adapter } @@ -45,7 +45,7 @@ class CanvasListFragment : Fragment() { enum class CanvasViewType { XFERS_MODE, CIRCULAR_AVATAR, SAVE_LAYER, SAVE_RESTORE } -class TestAdapter(private val entries: List, private val activity: AppCompatActivity) : +class CanvasListAdapter(private val entries: List, private val activity: AppCompatActivity) : RecyclerView.Adapter() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { diff --git a/app/src/main/java/com/example/associate/training/compose/ComposeAViewModel.kt b/app/src/main/java/com/example/associate/training/compose/ComposeAViewModel.kt new file mode 100644 index 0000000..92278a1 --- /dev/null +++ b/app/src/main/java/com/example/associate/training/compose/ComposeAViewModel.kt @@ -0,0 +1,22 @@ +package com.example.associate.training.compose + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.launch + +class ComposeAViewModel : ViewModel() { + private val _infoFlow = MutableSharedFlow(replay = 0) + val infoFlow: Flow = _infoFlow + + suspend fun sendEvent() { + _infoFlow.emit("I'm ComposeAViewModel") + } + + fun sendEventOutside() { + viewModelScope.launch { + sendEvent() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/associate/training/compose/ComposeActivity.kt b/app/src/main/java/com/example/associate/training/compose/ComposeActivity.kt new file mode 100644 index 0000000..0934aeb --- /dev/null +++ b/app/src/main/java/com/example/associate/training/compose/ComposeActivity.kt @@ -0,0 +1,71 @@ +package com.example.associate.training.compose +import android.os.Bundle +import android.util.Log +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.viewModels +import androidx.compose.foundation.layout.* +import androidx.compose.material.Button +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.launch + +class ComposeActivity: ComponentActivity() { + private val viewModelA: ComposeAViewModel by viewModels() + private val viewModelB: ComposeBViewModel by viewModels() + private val TAG = "ComposeActivity" + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + // MessageCard(" Android Tim ") + TwoButtons(viewModelA) + } + lifecycleScope.launch { + viewModelA.infoFlow.collect { key -> + Log.i(TAG, "viewModelB.sendEvent()") + viewModelB.sendEvent() + } + } + + lifecycleScope.launch { + viewModelB.infoFlow.collect { key -> + Log.i(TAG, "From: $key") + } + } + } +} +@Composable +fun TwoButtons(viewModelA: ComposeAViewModel) { + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Button(onClick = { + Log.i("ComposeActivity", "click A") + viewModelA.sendEventOutside() + }) { + Text(text = "Button 1") + } + Spacer(modifier = Modifier.height(16.dp)) + Button(onClick = { /* Handle button 2 click */ }) { + Text(text = "Button 2") + } + } +} +@Composable +fun MessageCard(name: String) { + Text(text = "Hello $name!") +} + +@Preview +@Composable +fun PreviewMessageCard12() { + MessageCard("Android") +} diff --git a/app/src/main/java/com/example/associate/training/compose/ComposeBViewModel.kt b/app/src/main/java/com/example/associate/training/compose/ComposeBViewModel.kt new file mode 100644 index 0000000..38694de --- /dev/null +++ b/app/src/main/java/com/example/associate/training/compose/ComposeBViewModel.kt @@ -0,0 +1,15 @@ +package com.example.associate.training.compose + +import androidx.lifecycle.ViewModel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow + +class ComposeBViewModel : ViewModel() { + private val _infoFlow = MutableSharedFlow(replay = 0) + val infoFlow: Flow = _infoFlow + + suspend fun sendEvent() { + _infoFlow.emit("I'm ComposeAViewModel") + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/associate/training/dummy/DummyRepository.kt b/app/src/main/java/com/example/associate/training/dummy/DummyRepository.kt index c73427e..0779d82 100644 --- a/app/src/main/java/com/example/associate/training/dummy/DummyRepository.kt +++ b/app/src/main/java/com/example/associate/training/dummy/DummyRepository.kt @@ -16,7 +16,7 @@ sealed class Result { // Create the repository to fetch the dummy api, and build the response data structure // TODO: Implement the coroutine flow as https://github.com/TouhidApps/Kotlin-Coroutine-Example // -class DummyRepository() { +class DummyRepository { private val loginUrl = "https://dummyapi.io/data/v1/user?limit=10" private val appID = "62adc6ad2aa7570c08535305" // Function that makes the network request, blocking the current thread diff --git a/app/src/main/java/com/example/associate/training/inventory/AddItemFragment.kt b/app/src/main/java/com/example/associate/training/inventory/AddItemFragment.kt index b351298..0b06ded 100644 --- a/app/src/main/java/com/example/associate/training/inventory/AddItemFragment.kt +++ b/app/src/main/java/com/example/associate/training/inventory/AddItemFragment.kt @@ -1,18 +1,3 @@ -/* - * Copyright (C) 2021 The Android Open Source Project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ package com.example.associate.training.inventory import android.content.Context.INPUT_METHOD_SERVICE @@ -26,7 +11,7 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs -import com.example.associate.training.busschedule.BusScheduleApplication +import com.example.associate.training.AssociateApplication import com.example.associate.training.databinding.FragmentAddItemBinding import com.example.associate.training.inventory.data.Item @@ -39,7 +24,7 @@ class AddItemFragment : Fragment() { // to share the ViewModel across fragments. private val viewModel: InventoryViewModel by activityViewModels { InventoryViewModelFactory( - (activity?.application as BusScheduleApplication).inventoryDatabase + (activity?.application as AssociateApplication).inventoryDatabase .itemDao() ) } diff --git a/app/src/main/java/com/example/associate/training/inventory/ItemDetailFragment.kt b/app/src/main/java/com/example/associate/training/inventory/ItemDetailFragment.kt index 97b0fbb..b6e9270 100644 --- a/app/src/main/java/com/example/associate/training/inventory/ItemDetailFragment.kt +++ b/app/src/main/java/com/example/associate/training/inventory/ItemDetailFragment.kt @@ -1,19 +1,3 @@ -/* - * Copyright (C) 2021 The Android Open Source Project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package com.example.associate.training.inventory import android.os.Bundle @@ -24,8 +8,8 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs +import com.example.associate.training.AssociateApplication import com.example.associate.training.R -import com.example.associate.training.busschedule.BusScheduleApplication import com.example.associate.training.databinding.FragmentItemDetailBinding import com.example.associate.training.inventory.data.Item import com.example.associate.training.inventory.data.getFormattedPrice @@ -41,7 +25,7 @@ class ItemDetailFragment : Fragment() { private val viewModel: InventoryViewModel by activityViewModels { InventoryViewModelFactory( - (activity?.application as BusScheduleApplication).inventoryDatabase.itemDao() + (activity?.application as AssociateApplication).inventoryDatabase.itemDao() ) } @@ -52,7 +36,7 @@ class ItemDetailFragment : Fragment() { inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { + ): View { _binding = FragmentItemDetailBinding.inflate(inflater, container, false) return binding.root } diff --git a/app/src/main/java/com/example/associate/training/inventory/ItemListFragment.kt b/app/src/main/java/com/example/associate/training/inventory/ItemListFragment.kt index 754cc52..e5952e5 100644 --- a/app/src/main/java/com/example/associate/training/inventory/ItemListFragment.kt +++ b/app/src/main/java/com/example/associate/training/inventory/ItemListFragment.kt @@ -1,19 +1,3 @@ -/* - * Copyright (C) 2021 The Android Open Source Project. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package com.example.associate.training.inventory import android.os.Bundle @@ -24,8 +8,8 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager +import com.example.associate.training.AssociateApplication import com.example.associate.training.R -import com.example.associate.training.busschedule.BusScheduleApplication import com.example.associate.training.databinding.FragmentItemListBinding /** @@ -34,7 +18,7 @@ import com.example.associate.training.databinding.FragmentItemListBinding class ItemListFragment : Fragment() { private val viewModel: InventoryViewModel by activityViewModels { InventoryViewModelFactory( - (activity?.application as BusScheduleApplication).inventoryDatabase.itemDao() + (activity?.application as AssociateApplication).inventoryDatabase.itemDao() ) } @@ -45,7 +29,7 @@ class ItemListFragment : Fragment() { inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { + ): View { _binding = FragmentItemListBinding.inflate(inflater, container, false) return binding.root } diff --git a/app/src/main/java/com/example/associate/training/koin/KoinActivity.kt b/app/src/main/java/com/example/associate/training/koin/KoinActivity.kt new file mode 100644 index 0000000..668f49a --- /dev/null +++ b/app/src/main/java/com/example/associate/training/koin/KoinActivity.kt @@ -0,0 +1,37 @@ +package com.example.associate.training.koin + +import android.os.Bundle +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.ViewModel +import com.example.associate.training.R +import org.koin.androidx.viewmodel.ext.android.viewModel + +class KoinActivity : AppCompatActivity() { + // Lazy Inject ViewModel + val myViewModel: MyViewModel by viewModel() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_koin) + } + + override fun onResume() { + super.onResume() + val textView: TextView = findViewById(R.id.koin_title) + textView.text = myViewModel.sayHello() + } +} + +interface HelloRepository { + fun giveHello(): String +} + +class HelloRepositoryImpl() : HelloRepository { + override fun giveHello() = "Hello Koin" +} + +class MyViewModel(val repo : HelloRepository) : ViewModel() { + + fun sayHello() = "${repo.giveHello()} from $this" +} \ No newline at end of file diff --git a/app/src/main/java/com/example/associate/training/lifecycle/aware/LiveDataTimerViewModel.java b/app/src/main/java/com/example/associate/training/lifecycle/aware/LiveDataTimerViewModel.java index 63e1580..6ea33ca 100644 --- a/app/src/main/java/com/example/associate/training/lifecycle/aware/LiveDataTimerViewModel.java +++ b/app/src/main/java/com/example/associate/training/lifecycle/aware/LiveDataTimerViewModel.java @@ -15,9 +15,9 @@ public class LiveDataTimerViewModel extends ViewModel { private static final int ONE_SECOND = 1000; - private MutableLiveData mElapsedTime = new MutableLiveData<>(); + private final MutableLiveData mElapsedTime = new MutableLiveData<>(); - private long mInitialTime; + private final long mInitialTime; private final Timer timer; public LiveDataTimerViewModel() { diff --git a/app/src/main/java/com/example/associate/training/lifecycle/aware/LocationActivity.java b/app/src/main/java/com/example/associate/training/lifecycle/aware/LocationActivity.java index 922938c..feec080 100644 --- a/app/src/main/java/com/example/associate/training/lifecycle/aware/LocationActivity.java +++ b/app/src/main/java/com/example/associate/training/lifecycle/aware/LocationActivity.java @@ -19,7 +19,7 @@ public class LocationActivity extends AppCompatActivity { private static final int REQUEST_LOCATION_PERMISSION_CODE = 1; - private LocationListener mGpsListener = new MyLocationListener(); + private final LocationListener mGpsListener = new MyLocationListener(); @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, diff --git a/app/src/main/java/com/example/associate/training/lifecycle/aware/SavedStateViewModel.java b/app/src/main/java/com/example/associate/training/lifecycle/aware/SavedStateViewModel.java index 2fcbb8e..e2c5709 100644 --- a/app/src/main/java/com/example/associate/training/lifecycle/aware/SavedStateViewModel.java +++ b/app/src/main/java/com/example/associate/training/lifecycle/aware/SavedStateViewModel.java @@ -24,7 +24,7 @@ public class SavedStateViewModel extends ViewModel { private static final String NAME_KEY = "name"; - private SavedStateHandle mState; + private final SavedStateHandle mState; public SavedStateViewModel(SavedStateHandle savedStateHandle) { mState = savedStateHandle; diff --git a/app/src/main/java/com/example/associate/training/media/MediaActivity.kt b/app/src/main/java/com/example/associate/training/media/MediaActivity.kt new file mode 100644 index 0000000..aab2631 --- /dev/null +++ b/app/src/main/java/com/example/associate/training/media/MediaActivity.kt @@ -0,0 +1,79 @@ +package com.example.associate.training.media + +import android.animation.ObjectAnimator +import android.animation.PropertyValuesHolder +import android.animation.ValueAnimator +import android.graphics.* +import android.os.Build +import android.os.Bundle +import android.os.Environment +import android.widget.ImageView +import androidx.annotation.RequiresApi +import androidx.appcompat.app.AppCompatActivity +import com.example.associate.training.databinding.ActivityMediaBinding +import org.koin.core.parameter.parametersOf +import org.koin.android.ext.android.inject +import java.io.File +import java.util.* + +class MediaActivity : AppCompatActivity(), ContextCallback { + + private lateinit var binding: ActivityMediaBinding + val viewModel: MediaViewModel by inject { parametersOf(720, 1280) } + + @RequiresApi(Build.VERSION_CODES.M) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityMediaBinding.inflate(layoutInflater) + setContentView(binding.root) + setupViewModel() + setListener() + grantPermission() + } + + private fun setupViewModel() { + // Use the ViewModelProvider to get an instance of MediaViewModel + viewModel.setCallback(this) + viewModel.state.observe(this) { state -> + when (state) { + is EncodingState.Init -> showToast("Init") + is EncodingState.Loading -> showLoading() + is EncodingState.Complete -> showToast("Complete") + } + } + } + + private fun showLoading() { + binding.progressIndicator.isIndeterminate = true + } + + private fun showToast(text: String) { + binding.progressIndicator.isIndeterminate = false + binding.content.text = text + } + + // Grant permission to write to external storage + @RequiresApi(Build.VERSION_CODES.M) + private fun grantPermission() { + val permission = android.Manifest.permission.WRITE_EXTERNAL_STORAGE + val grant = checkCallingOrSelfPermission(permission) + if (grant != android.content.pm.PackageManager.PERMISSION_GRANTED) { + requestPermissions(arrayOf(permission), 1) + } + } + + private fun setListener() { + binding.exportToVideo.setOnClickListener { + viewModel.startEncoding() + } + } + + override fun getBitmap(drawable: Int): Bitmap { + return BitmapFactory.decodeResource(resources, drawable) + } + + override fun getFile(): File { + return File(getExternalFilesDir(Environment.DIRECTORY_MOVIES), "myvideo.mp4") + } + +} diff --git a/app/src/main/java/com/example/associate/training/media/MediaViewModel.kt b/app/src/main/java/com/example/associate/training/media/MediaViewModel.kt new file mode 100644 index 0000000..c13a626 --- /dev/null +++ b/app/src/main/java/com/example/associate/training/media/MediaViewModel.kt @@ -0,0 +1,166 @@ +package com.example.associate.training.media + +import android.graphics.* +import android.media.MediaCodec +import android.media.MediaCodecInfo +import android.media.MediaFormat +import android.media.MediaMuxer +import android.util.Log +import android.view.Surface +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.example.associate.training.R +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import java.io.File + +interface ContextCallback { + fun getBitmap(drawable: Int): Bitmap + fun getFile(): File +} + +sealed class EncodingState { + object Init : EncodingState() + object Loading : EncodingState() + object Complete : EncodingState() +} +class MediaViewModel(private val height: Int, private val width: Int) : ViewModel() { + private val TAG = this::class.simpleName + private var videoTrackIndex: Int = 0 + private var callback: ContextCallback? = null + private val _state = MutableLiveData() + val state: LiveData = _state + + fun setCallback(callback: ContextCallback) { + this.callback = callback + } + + // Encode the zoomIn animation for 5 durations + fun startEncoding() { + if (_state.value == EncodingState.Loading) return + _state.postValue(EncodingState.Loading) + Log.i("Tim", "height $height, width $width") + CoroutineScope(Dispatchers.IO).launch { + try { + val bitrate = 3000000 + val mime = "video/avc" + val format = MediaFormat.createVideoFormat(mime, width, height) + format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate) + format.setInteger(MediaFormat.KEY_FRAME_RATE, 30) + format.setInteger( + MediaFormat.KEY_COLOR_FORMAT, + MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface + ) + format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1) + val codec = MediaCodec.createEncoderByType(mime) + codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE) + val surface: Surface = codec.createInputSurface() + codec.start() + // Create MediaMuxer and add track + val videoPath = createTempFile() + val muxer = MediaMuxer(videoPath.path, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4) + val bufferInfo = MediaCodec.BufferInfo() + + encode(surface, bufferInfo, codec, muxer) + // Release resources + codec.stop() + codec.release() + muxer.stop() + muxer.release() + } catch (e: Exception) { + Log.e(TAG, "Error: ${e.message}") + } finally { + _state.postValue(EncodingState.Complete) + } + } + } + + private fun renderBitmap(drawable: Int, surface: Surface) { + callback?.let { + val bitmap = it.getBitmap(drawable) + val scaledBitmap = Bitmap.createScaledBitmap(bitmap, width, height, true) + val canvas: Canvas = surface.lockCanvas(null) + canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR) + canvas.drawBitmap(scaledBitmap, 0.0f, 0.0f, null) + surface.unlockCanvasAndPost(canvas) + } + } + + private fun encode( + surface: Surface, + bufferInfo: MediaCodec.BufferInfo, + encoder: MediaCodec, + muxer: MediaMuxer + ) { + val dequeueTimeoutUsec = 10000L + val videoDuration = 5 * 1000000L + var startTime: Long = -1 + var isEndOfStream = false + val drawableList = listOf(R.drawable.birthday, R.drawable.birthday_cakes, R.drawable.happy_birthday) + var index = 0 + while (true) { + renderBitmap(drawableList[index], surface) + val outputBufferIndex = encoder.dequeueOutputBuffer(bufferInfo, dequeueTimeoutUsec) + val presentationTimeUs = bufferInfo.presentationTimeUs + if (startTime == -1L && presentationTimeUs > 0) { + startTime = presentationTimeUs + } else if (startTime != -1L) { + if (presentationTimeUs - startTime > videoDuration && !isEndOfStream) { + encoder.signalEndOfInputStream() + isEndOfStream = true + } else { + index = (presentationTimeUs / 1000000L).toInt() % drawableList.size + } + } + if (outputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) { + Log.i(TAG, "no output available, spinning to await EOS") + //break + } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { + Log.i(TAG, "start muxer") + videoTrackIndex = muxer.addTrack(encoder.outputFormat) + muxer.start() + } else if (outputBufferIndex < 0) { + Log.i(TAG, "unexpected result from encoder.dequeueOutputBuffer") + } else { + val encodedDataBuffer = encoder.getOutputBuffer(outputBufferIndex) + ?: throw RuntimeException("encoderOutputBuffer $outputBufferIndex was null") + + if (bufferInfo.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG != 0) { + Log.i(TAG, "ignoring BUFFER_FLAG_CODEC_CONFIG") + bufferInfo.size = 0 + } + + if (bufferInfo.size != 0) { + // adjust the ByteBuffer values to match BufferInfo + encodedDataBuffer.position(bufferInfo.offset) + encodedDataBuffer.limit(bufferInfo.offset + bufferInfo.size) + muxer.writeSampleData(videoTrackIndex, encodedDataBuffer, bufferInfo) + } + + encoder.releaseOutputBuffer(outputBufferIndex, false) + + if (bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) { + Log.i(TAG, "Get EOS") + break + } + } + } + } + + private fun createTempFile(): File { + + callback?.let { + val file = it.getFile() + Log.i(TAG, "file: ${file.absolutePath}") + if (file.exists()) { + file.delete() + } + file.createNewFile() + return file + } + + throw RuntimeException("file not found") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/associate/training/misc/MiscActivity.kt b/app/src/main/java/com/example/associate/training/misc/MiscActivity.kt new file mode 100644 index 0000000..a86f0a4 --- /dev/null +++ b/app/src/main/java/com/example/associate/training/misc/MiscActivity.kt @@ -0,0 +1,26 @@ +package com.example.associate.training.misc + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.add +import androidx.fragment.app.commit +import com.example.associate.training.R +import com.example.associate.training.misc.fragment.MiscListFragment + +class MiscActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + createFragment() + } + + private fun createFragment() { + setContentView(R.layout.activity_misc) + supportFragmentManager.commit { + setReorderingAllowed(true) + add(R.id.canvas_fragment_container) + } + } + +} + diff --git a/app/src/main/java/com/example/associate/training/misc/fragment/MiscListFragment.kt b/app/src/main/java/com/example/associate/training/misc/fragment/MiscListFragment.kt new file mode 100644 index 0000000..2eaff8f --- /dev/null +++ b/app/src/main/java/com/example/associate/training/misc/fragment/MiscListFragment.kt @@ -0,0 +1,76 @@ +package com.example.associate.training.misc.fragment + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.annotation.Nullable +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.Fragment +import androidx.fragment.app.commit +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.example.associate.training.R +import com.example.associate.training.databinding.ActivityMainBinding + +class MiscListFragment : Fragment() { + + private lateinit var adapter: MiscListAdapter + private lateinit var binding: ActivityMainBinding + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupAdapter() + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = ActivityMainBinding.inflate(layoutInflater) + return binding.root + } + + private fun setupAdapter() { + adapter = MiscListAdapter(MiscViewType.values().toList(), activity as AppCompatActivity) + binding.tutorsRecyclerView.layoutManager = LinearLayoutManager(binding.root.context) + binding.tutorsRecyclerView.adapter = adapter + } + +} + +enum class MiscViewType { VIEWSTUB, OTHER} + +class MiscListAdapter(private val entries: List, private val activity: AppCompatActivity) : + RecyclerView.Adapter() { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val view = LayoutInflater.from(parent.context) + .inflate(R.layout.card_view_timeline, parent, false) + return DesignViewHolder(view) + } + + override fun onBindViewHolder(@Nullable holder: RecyclerView.ViewHolder, position: Int) { + if (holder is DesignViewHolder) { + val entry = entries[position] + holder.textView.text = entry.name + holder.itemView.setOnClickListener { + activity.supportFragmentManager.commit { + replace(R.id.canvas_fragment_container, ViewStubFragment()) + addToBackStack(null) + } + } + } + + } + + override fun getItemCount(): Int { + return entries.size + } + + class DesignViewHolder(ItemView: View) : RecyclerView.ViewHolder(ItemView) { + val textView: TextView = itemView.findViewById(R.id.textView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/associate/training/misc/fragment/ViewStubFragment.kt b/app/src/main/java/com/example/associate/training/misc/fragment/ViewStubFragment.kt new file mode 100644 index 0000000..b37c9ea --- /dev/null +++ b/app/src/main/java/com/example/associate/training/misc/fragment/ViewStubFragment.kt @@ -0,0 +1,43 @@ +package com.example.associate.training.misc.fragment + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.example.associate.training.R +import com.example.associate.training.canvas.CircularAvatarView +import com.example.associate.training.canvas.SaveLayerView +import com.example.associate.training.canvas.SaveRestoreCanvasView +import com.example.associate.training.canvas.XfersModeView +import com.example.associate.training.canvas.view.CanvasViewType +import com.example.associate.training.databinding.ActivityMainBinding +import com.example.associate.training.databinding.FragmentViewStubBinding + +class ViewStubFragment : Fragment() { + private lateinit var binding: FragmentViewStubBinding + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = FragmentViewStubBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding.apply { + helloWorld.setOnClickListener { + viewStub.layoutResource = R.layout.hello_world + viewStub.inflate() + } + + hellWorld.setOnClickListener { + viewStub.layoutResource = R.layout.hell_world + viewStub.inflate() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/associate/training/views/ViewsActivity.kt b/app/src/main/java/com/example/associate/training/views/ViewsActivity.kt new file mode 100644 index 0000000..017c82b --- /dev/null +++ b/app/src/main/java/com/example/associate/training/views/ViewsActivity.kt @@ -0,0 +1,25 @@ +package com.example.associate.training.views + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.add +import androidx.fragment.app.commit +import com.example.associate.training.R +import com.example.associate.training.views.fragment.ViewsFragment + +class ViewsActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + createFragment() + } + + private fun createFragment() { + setContentView(R.layout.activity_canvas) + supportFragmentManager.commit { + setReorderingAllowed(true) + add(R.id.canvas_fragment_container) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/associate/training/views/fragment/TabLayoutFragment.kt b/app/src/main/java/com/example/associate/training/views/fragment/TabLayoutFragment.kt new file mode 100644 index 0000000..35cee73 --- /dev/null +++ b/app/src/main/java/com/example/associate/training/views/fragment/TabLayoutFragment.kt @@ -0,0 +1,47 @@ +package com.example.associate.training.views.fragment + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.Menu +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.Fragment +import androidx.navigation.fragment.findNavController +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.viewpager.widget.ViewPager +import com.example.associate.training.R +import com.example.associate.training.canvas.CircularAvatarView +import com.example.associate.training.canvas.SaveLayerView +import com.example.associate.training.canvas.SaveRestoreCanvasView +import com.example.associate.training.canvas.XfersModeView +import com.example.associate.training.canvas.view.CanvasViewType +import com.example.associate.training.databinding.ActivityMainBinding +import com.example.associate.training.databinding.FragmentViewsBinding +import com.example.associate.training.inventory.ItemListAdapter +import com.example.associate.training.inventory.ItemListFragmentDirections +import com.example.associate.training.views.layout.ViewPagerAdapter +import com.google.android.material.tabs.TabLayout + +class TabLayoutFragment : Fragment() { + private lateinit var viewPagerAdapter: ViewPagerAdapter + private lateinit var binding: FragmentViewsBinding + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = FragmentViewsBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val viewPager = binding.viewPager + + viewPagerAdapter = ViewPagerAdapter(requireActivity().supportFragmentManager) + viewPager.adapter = viewPagerAdapter + binding.tabs.setupWithViewPager(viewPager) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/associate/training/views/fragment/ViewsFragment.kt b/app/src/main/java/com/example/associate/training/views/fragment/ViewsFragment.kt new file mode 100644 index 0000000..31521ca --- /dev/null +++ b/app/src/main/java/com/example/associate/training/views/fragment/ViewsFragment.kt @@ -0,0 +1,77 @@ +package com.example.associate.training.views.fragment + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.annotation.Nullable +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.Fragment +import androidx.fragment.app.commit +import androidx.fragment.app.replace +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.example.associate.training.R +import com.example.associate.training.databinding.ActivityMainBinding + +class ViewsFragment : Fragment() { + + private lateinit var adapter: ViewsListAdapter + private lateinit var binding: ActivityMainBinding + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupAdapter() + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = ActivityMainBinding.inflate(layoutInflater) + return binding.root + } + + private fun setupAdapter() { + adapter = ViewsListAdapter(ViewsType.values().toList(), activity as AppCompatActivity) + binding.tutorsRecyclerView.layoutManager = LinearLayoutManager(binding.root.context) + binding.tutorsRecyclerView.adapter = adapter + } + +} + +enum class ViewsType { TAB_LAYOUT_1, TAB_LAYOUT_2, TAB_LAYOUT_3, TAB_LAYOUT_4 } + +class ViewsListAdapter(private val entries: List, private val activity: AppCompatActivity) : + RecyclerView.Adapter() { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val view = LayoutInflater.from(parent.context) + .inflate(R.layout.card_view_timeline, parent, false) + return DesignViewHolder(view) + } + + override fun onBindViewHolder(@Nullable holder: RecyclerView.ViewHolder, position: Int) { + if (holder is DesignViewHolder) { + val entry = entries[position] + holder.textView.text = entry.name + holder.itemView.setOnClickListener { + activity.supportFragmentManager.commit { + replace(R.id.canvas_fragment_container) + addToBackStack(null) + } + } + } + + } + + override fun getItemCount(): Int { + return entries.size + } + + class DesignViewHolder(ItemView: View) : RecyclerView.ViewHolder(ItemView) { + val textView: TextView = itemView.findViewById(R.id.textView) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/associate/training/views/layout/FragmentA.kt b/app/src/main/java/com/example/associate/training/views/layout/FragmentA.kt new file mode 100755 index 0000000..3052589 --- /dev/null +++ b/app/src/main/java/com/example/associate/training/views/layout/FragmentA.kt @@ -0,0 +1,35 @@ +package com.example.associate.training.views.layout + +import android.view.LayoutInflater +import android.view.ViewGroup +import android.os.Bundle +import android.view.View +import android.widget.ListView +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.example.associate.training.R + +class FragmentA : Fragment() { + var recyclerView: RecyclerView? = null + var list: ListView? = null + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val items = resources.getStringArray(R.array.tab_A) + val adapter = RecyclerViewAdapter(items) + recyclerView = view.findViewById(R.id.recycler_view) as RecyclerView + val layoutManager = LinearLayoutManager( + context + ) + recyclerView!!.layoutManager = layoutManager + recyclerView!!.adapter = adapter + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/associate/training/views/layout/FragmentB.kt b/app/src/main/java/com/example/associate/training/views/layout/FragmentB.kt new file mode 100755 index 0000000..49cd36b --- /dev/null +++ b/app/src/main/java/com/example/associate/training/views/layout/FragmentB.kt @@ -0,0 +1,33 @@ +package com.example.associate.training.views.layout + +import android.view.LayoutInflater +import android.view.ViewGroup +import android.os.Bundle +import android.view.View +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.example.associate.training.R + +class FragmentB : Fragment() { + var recyclerView: RecyclerView? = null + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val items = resources.getStringArray(R.array.tab_B) + val adapter = RecyclerViewAdapter(items) + recyclerView = view.findViewById(R.id.recycler_view) as RecyclerView + val layoutManager: RecyclerView.LayoutManager = LinearLayoutManager( + context + ) + recyclerView!!.layoutManager = layoutManager + recyclerView!!.adapter = adapter + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/associate/training/views/layout/FragmentC.kt b/app/src/main/java/com/example/associate/training/views/layout/FragmentC.kt new file mode 100755 index 0000000..f3cfe0d --- /dev/null +++ b/app/src/main/java/com/example/associate/training/views/layout/FragmentC.kt @@ -0,0 +1,31 @@ +package com.example.associate.training.views.layout + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.example.associate.training.R + +class FragmentC : Fragment() { + var recyclerView: RecyclerView? = null + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + return inflater.inflate(R.layout.fragment, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val items: Array = getResources().getStringArray(R.array.tab_C) + val adapter = RecyclerViewAdapter(items) + recyclerView = view.findViewById(R.id.recycler_view) as RecyclerView + val layoutManager = LinearLayoutManager(getContext()) + recyclerView!!.setLayoutManager(layoutManager) + recyclerView!!.setAdapter(adapter) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/associate/training/views/layout/RecyclerViewAdapter.kt b/app/src/main/java/com/example/associate/training/views/layout/RecyclerViewAdapter.kt new file mode 100644 index 0000000..0761421 --- /dev/null +++ b/app/src/main/java/com/example/associate/training/views/layout/RecyclerViewAdapter.kt @@ -0,0 +1,27 @@ +package com.example.associate.training.views.layout + +import android.view.ViewGroup +import android.view.LayoutInflater +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import com.example.associate.training.R + +class RecyclerViewAdapter(var items: Array) : RecyclerView.Adapter() { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TextItemViewHolder { + val view: View = LayoutInflater.from(parent.context) + .inflate(R.layout.recycler_view_list_item, parent, false) + return TextItemViewHolder(view) + } + + override fun onBindViewHolder(holder: TextItemViewHolder, position: Int) { + holder.bind(items[position]) + } + + override fun getItemId(position: Int): Long { + return position.toLong() + } + + override fun getItemCount(): Int { + return items.size + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/associate/training/views/layout/TextItemViewHolder.kt b/app/src/main/java/com/example/associate/training/views/layout/TextItemViewHolder.kt new file mode 100755 index 0000000..21c65e1 --- /dev/null +++ b/app/src/main/java/com/example/associate/training/views/layout/TextItemViewHolder.kt @@ -0,0 +1,16 @@ +package com.example.associate.training.views.layout +import android.view.View +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.example.associate.training.R + +class TextItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + private val textView: TextView + fun bind(text: String?) { + textView.setText(text) + } + + init { + textView = itemView.findViewById(R.id.list_item) as TextView + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/associate/training/views/layout/ViewPagerAdapter.kt b/app/src/main/java/com/example/associate/training/views/layout/ViewPagerAdapter.kt new file mode 100755 index 0000000..282e33f --- /dev/null +++ b/app/src/main/java/com/example/associate/training/views/layout/ViewPagerAdapter.kt @@ -0,0 +1,36 @@ +package com.example.associate.training.views.layout + +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.FragmentPagerAdapter + +class ViewPagerAdapter(fm: FragmentManager) : FragmentPagerAdapter(fm) { + + override fun getItem(position: Int): Fragment { + var fragment: Fragment? = null + if (position == 0) { + fragment = FragmentA() + } else if (position == 1) { + fragment = FragmentB() + } else if (position == 2) { + fragment = FragmentC() + } + return fragment!! + } + + override fun getCount(): Int { + return 3 + } + + override fun getPageTitle(position: Int): CharSequence? { + var title: String? = null + if (position == 0) { + title = "Tab-1" + } else if (position == 1) { + title = "Tab-2" + } else if (position == 2) { + title = "Tab-3" + } + return title + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/associate/training/word/LetterAdapter.kt b/app/src/main/java/com/example/associate/training/word/LetterAdapter.kt index a9faf3f..e68ff52 100644 --- a/app/src/main/java/com/example/associate/training/word/LetterAdapter.kt +++ b/app/src/main/java/com/example/associate/training/word/LetterAdapter.kt @@ -81,8 +81,8 @@ class LetterAdapter : companion object Accessibility : View.AccessibilityDelegate() { @RequiresApi(Build.VERSION_CODES.LOLLIPOP) override fun onInitializeAccessibilityNodeInfo( - host: View?, - info: AccessibilityNodeInfo? + host: View, + info: AccessibilityNodeInfo ) { super.onInitializeAccessibilityNodeInfo(host, info) // With `null` as the second argument to [AccessibilityAction], the diff --git a/app/src/main/java/com/example/associate/training/word/WordAdapter.kt b/app/src/main/java/com/example/associate/training/word/WordAdapter.kt index a896f68..1f41117 100644 --- a/app/src/main/java/com/example/associate/training/word/WordAdapter.kt +++ b/app/src/main/java/com/example/associate/training/word/WordAdapter.kt @@ -68,7 +68,7 @@ class WordAdapter(private val letterId: String, context: Context) : .inflate(R.layout.item_view, parent, false) // Setup custom accessibility delegate to set the text read - layout.accessibilityDelegate = Accessibility + // layout.accessibilityDelegate = Accessibility return WordViewHolder(layout) } @@ -98,8 +98,8 @@ class WordAdapter(private val letterId: String, context: Context) : companion object Accessibility : View.AccessibilityDelegate() { @RequiresApi(Build.VERSION_CODES.LOLLIPOP) override fun onInitializeAccessibilityNodeInfo( - host: View?, - info: AccessibilityNodeInfo? + host: View, + info: AccessibilityNodeInfo ) { super.onInitializeAccessibilityNodeInfo(host, info) // With `null` as the second argument to [AccessibilityAction], the diff --git a/app/src/main/java/com/example/associate/training/workmanager/BlurViewModel.kt b/app/src/main/java/com/example/associate/training/workmanager/BlurViewModel.kt index 7cbc6ea..5cb3f64 100644 --- a/app/src/main/java/com/example/associate/training/workmanager/BlurViewModel.kt +++ b/app/src/main/java/com/example/associate/training/workmanager/BlurViewModel.kt @@ -135,7 +135,7 @@ class BlurViewModel(application: Application) : ViewModel() { class BlurViewModelFactory(private val application: Application) : ViewModelProvider.Factory { - override fun create(modelClass: Class): T { + override fun create(modelClass: Class): T { return if (modelClass.isAssignableFrom(BlurViewModel::class.java)) { BlurViewModel(application) as T } else { diff --git a/app/src/main/res/drawable/birthday.png b/app/src/main/res/drawable/birthday.png new file mode 100644 index 0000000..8d1e021 Binary files /dev/null and b/app/src/main/res/drawable/birthday.png differ diff --git a/app/src/main/res/drawable/birthday_cakes.png b/app/src/main/res/drawable/birthday_cakes.png new file mode 100644 index 0000000..b7d4f7c Binary files /dev/null and b/app/src/main/res/drawable/birthday_cakes.png differ diff --git a/app/src/main/res/drawable/happy_birthday.png b/app/src/main/res/drawable/happy_birthday.png new file mode 100644 index 0000000..1bcff97 Binary files /dev/null and b/app/src/main/res/drawable/happy_birthday.png differ diff --git a/app/src/main/res/drawable/tab_background.xml b/app/src/main/res/drawable/tab_background.xml new file mode 100644 index 0000000..a0f112a --- /dev/null +++ b/app/src/main/res/drawable/tab_background.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/tab_selected_background.xml b/app/src/main/res/drawable/tab_selected_background.xml new file mode 100644 index 0000000..68f5b99 --- /dev/null +++ b/app/src/main/res/drawable/tab_selected_background.xml @@ -0,0 +1,14 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/tab_selected_false_background.xml b/app/src/main/res/drawable/tab_selected_false_background.xml new file mode 100644 index 0000000..20e8549 --- /dev/null +++ b/app/src/main/res/drawable/tab_selected_false_background.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_blur.xml b/app/src/main/res/layout/activity_blur.xml index ab4f0c0..ca6ba87 100644 --- a/app/src/main/res/layout/activity_blur.xml +++ b/app/src/main/res/layout/activity_blur.xml @@ -42,7 +42,6 @@ android:text="@string/blur_title" android:textAppearance="@style/TextAppearance.AppCompat.Large" /> - + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 19fd628..688a574 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,7 +1,6 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_misc.xml b/app/src/main/res/layout/activity_misc.xml new file mode 100644 index 0000000..8c15e7d --- /dev/null +++ b/app/src/main/res/layout/activity_misc.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/app/src/main/res/layout/activity_view.xml b/app/src/main/res/layout/activity_view.xml new file mode 100644 index 0000000..1c42628 --- /dev/null +++ b/app/src/main/res/layout/activity_view.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/app/src/main/res/layout/fragment.xml b/app/src/main/res/layout/fragment.xml new file mode 100755 index 0000000..99443e4 --- /dev/null +++ b/app/src/main/res/layout/fragment.xml @@ -0,0 +1,5 @@ + + diff --git a/app/src/main/res/layout/fragment_list.xml b/app/src/main/res/layout/fragment_list.xml new file mode 100755 index 0000000..d1580b0 --- /dev/null +++ b/app/src/main/res/layout/fragment_list.xml @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_view_stub.xml b/app/src/main/res/layout/fragment_view_stub.xml new file mode 100644 index 0000000..56529d3 --- /dev/null +++ b/app/src/main/res/layout/fragment_view_stub.xml @@ -0,0 +1,27 @@ + + + + +