diff --git a/app-catalog/app/build.gradle b/app-catalog/app/build.gradle index 0ebc6d1..7b07b4e 100644 --- a/app-catalog/app/build.gradle +++ b/app-catalog/app/build.gradle @@ -33,8 +33,8 @@ android { applicationId "com.wintmain.catalog.app" minSdk 26 targetSdk 34 - versionCode 20241009 - versionName 'V20241009' + versionCode 20241020 + versionName 'V20241020' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/app-catalog/samples/foundation/build.gradle b/app-catalog/samples/foundation/build.gradle index a29880f..c247b54 100644 --- a/app-catalog/samples/foundation/build.gradle +++ b/app-catalog/samples/foundation/build.gradle @@ -17,42 +17,18 @@ android { } dependencies { - // implementation fileTree(include: ['*.jar', "*.aar"], dir: "libs") implementation project(path: ':app-catalog:samples:xpermissions:libxpermissions') implementation project(path: ':app-catalog:samples:foundation:libfoundation') + implementation project(path: ':app-catalog:samples:toaster:libtoaster') + implementation project(path: ':app-catalog:samples:titlebar:libtitlebar') + implementation libs.androidx.constraintlayout implementation libs.androidx.activity.compose implementation libs.casa.ui implementation libs.androidx.recyclerviewExt implementation libs.androidx.cardview - - // 标题栏框架:https://github.com/getActivity/TitleBar - // TBD - replace with self - // 会有资源冲突的问题,1是解决本地,2是将引用更新到最新 - implementation 'com.github.getActivity:TitleBar:10.5' - - // 权限请求框架:https://github.com/getActivity/XXPermissions - // TBD - replace with self -// implementation 'com.github.getActivity:XXPermissions:18.5' - - // 吐司框架:https://github.com/getActivity/Toaster - // TBD - replace with self - // 直接使用aar文件,选择build module会报这个错误,虽然不影响整个app编译 - // Direct local .aar file dependencies are not supported when building an AAR. - // The resulting AAR would be broken because the classes and Android resources from any local .aar file dependencies - // would not be packaged in the resulting AAR. Previous versions of the Android Gradle Plugin produce broken AARs - // in this case too (despite not throwing this error). - // The following direct local .aar file dependencies of the :app-catalog:samples:androidpft project - // caused this error: .../app-catalog/samples/androidpft/libs/Toaster-12.5.aar - implementation 'com.github.getActivity:Toaster:12.5' - - // 上拉刷新下拉加载框架:https://github.com/scwang90/SmartRefreshLayout -// implementation 'io.github.scwang90:refresh-layout-kernel:2.1.0' //核心必须依赖 -// implementation 'io.github.scwang90:refresh-header-material:2.1.0' //谷歌刷新头 - implementation libs.androidx.navigation.fragment implementation libs.androidx.navigation.ui implementation libs.androidx.slidingpanelayout - - implementation "io.coil-kt:coil:2.6.0" + implementation libs.coil } \ No newline at end of file diff --git a/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/views/recyclerview/logger/Log.java b/app-catalog/samples/foundation/libfoundation/src/main/java/lib/wintmain/foundation/logger/Log.java similarity index 99% rename from app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/views/recyclerview/logger/Log.java rename to app-catalog/samples/foundation/libfoundation/src/main/java/lib/wintmain/foundation/logger/Log.java index 88bbd6e..ab9e49e 100644 --- a/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/views/recyclerview/logger/Log.java +++ b/app-catalog/samples/foundation/libfoundation/src/main/java/lib/wintmain/foundation/logger/Log.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.wintmain.foundation.views.recyclerview.logger; +package lib.wintmain.foundation.logger; /** * Helper class for a list (or tree) of LoggerNodes. diff --git a/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/views/recyclerview/logger/LogFragment.java b/app-catalog/samples/foundation/libfoundation/src/main/java/lib/wintmain/foundation/logger/LogFragment.java similarity index 98% rename from app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/views/recyclerview/logger/LogFragment.java rename to app-catalog/samples/foundation/libfoundation/src/main/java/lib/wintmain/foundation/logger/LogFragment.java index b9117e5..1f863b0 100644 --- a/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/views/recyclerview/logger/LogFragment.java +++ b/app-catalog/samples/foundation/libfoundation/src/main/java/lib/wintmain/foundation/logger/LogFragment.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.wintmain.foundation.views.recyclerview.logger; +package lib.wintmain.foundation.logger; import android.graphics.Typeface; import android.os.Bundle; diff --git a/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/views/recyclerview/logger/LogNode.java b/app-catalog/samples/foundation/libfoundation/src/main/java/lib/wintmain/foundation/logger/LogNode.java similarity index 96% rename from app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/views/recyclerview/logger/LogNode.java rename to app-catalog/samples/foundation/libfoundation/src/main/java/lib/wintmain/foundation/logger/LogNode.java index 9d0a6a9..1c5e11b 100644 --- a/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/views/recyclerview/logger/LogNode.java +++ b/app-catalog/samples/foundation/libfoundation/src/main/java/lib/wintmain/foundation/logger/LogNode.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.wintmain.foundation.views.recyclerview.logger; +package lib.wintmain.foundation.logger; /** * Basic interface for a logging system that can output to one or more targets. diff --git a/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/views/recyclerview/logger/LogView.java b/app-catalog/samples/foundation/libfoundation/src/main/java/lib/wintmain/foundation/logger/LogView.java similarity index 98% rename from app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/views/recyclerview/logger/LogView.java rename to app-catalog/samples/foundation/libfoundation/src/main/java/lib/wintmain/foundation/logger/LogView.java index 7b4c016..6d41aef 100644 --- a/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/views/recyclerview/logger/LogView.java +++ b/app-catalog/samples/foundation/libfoundation/src/main/java/lib/wintmain/foundation/logger/LogView.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.wintmain.foundation.views.recyclerview.logger; +package lib.wintmain.foundation.logger; import android.app.Activity; import android.content.Context; diff --git a/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/views/recyclerview/logger/LogWrapper.java b/app-catalog/samples/foundation/libfoundation/src/main/java/lib/wintmain/foundation/logger/LogWrapper.java similarity index 97% rename from app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/views/recyclerview/logger/LogWrapper.java rename to app-catalog/samples/foundation/libfoundation/src/main/java/lib/wintmain/foundation/logger/LogWrapper.java index ee4ebd5..402fe7e 100644 --- a/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/views/recyclerview/logger/LogWrapper.java +++ b/app-catalog/samples/foundation/libfoundation/src/main/java/lib/wintmain/foundation/logger/LogWrapper.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.wintmain.foundation.views.recyclerview.logger; +package lib.wintmain.foundation.logger; import android.util.Log; diff --git a/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/views/recyclerview/logger/MessageOnlyLogFilter.java b/app-catalog/samples/foundation/libfoundation/src/main/java/lib/wintmain/foundation/logger/MessageOnlyLogFilter.java similarity index 96% rename from app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/views/recyclerview/logger/MessageOnlyLogFilter.java rename to app-catalog/samples/foundation/libfoundation/src/main/java/lib/wintmain/foundation/logger/MessageOnlyLogFilter.java index bbded4e..aedf53b 100644 --- a/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/views/recyclerview/logger/MessageOnlyLogFilter.java +++ b/app-catalog/samples/foundation/libfoundation/src/main/java/lib/wintmain/foundation/logger/MessageOnlyLogFilter.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.wintmain.foundation.views.recyclerview.logger; +package lib.wintmain.foundation.logger; /** * Simple {@link LogNode} filter, removes everything except the message. diff --git a/app-catalog/samples/foundation/src/main/AndroidManifest.xml b/app-catalog/samples/foundation/src/main/AndroidManifest.xml index 91e872c..3b24c10 100644 --- a/app-catalog/samples/foundation/src/main/AndroidManifest.xml +++ b/app-catalog/samples/foundation/src/main/AndroidManifest.xml @@ -1,4 +1,5 @@ - @@ -32,10 +34,12 @@ android:name=".telephony.callnotification.CallNotificationSample" android:exported="true" android:launchMode="singleTop" - android:theme="@style/Theme.AppCompat.DayNight" /> + android:theme="@style/Theme.AppCompat.DayNight" + tools:targetApi="28" /> + android:exported="false" + tools:targetApi="28" /> @@ -52,6 +56,13 @@ android:theme="@style/Theme.AppCompat.DayNight" /> + + + + (R.id.sample_output_gesture)?.apply { + isClickable = true + isFocusable = true + }?.setOnTouchListener { _, motionEvent -> + gd.onTouchEvent(motionEvent) + false + } + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + if (item.itemId == R.id.sample_action_gesture) { + clearLog() + } + return true + } + + private fun clearLog() { + val logFragment = (requireActivity().supportFragmentManager + .findFragmentById(R.id.log_fragment_gesture) as LogFragment?) + logFragment?.logView?.text = "" + } +} diff --git a/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/views/gesture/GestureListener.kt b/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/views/gesture/GestureListener.kt new file mode 100644 index 0000000..e1dcd71 --- /dev/null +++ b/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/views/gesture/GestureListener.kt @@ -0,0 +1,155 @@ +/* + * Copyright 2023-2024 wintmain + * + * 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 + * + * https://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.wintmain.foundation.views.gesture + +import android.os.Build.VERSION +import android.view.GestureDetector.SimpleOnGestureListener +import android.view.MotionEvent +import lib.wintmain.foundation.logger.Log + +class GestureListener : SimpleOnGestureListener() { + companion object { + const val TAG: String = "GestureListener" + + /** + * Returns a human-readable string describing the type of touch that triggered a MotionEvent. + */ + private fun getTouchType(e: MotionEvent?): String { + var touchTypeDescription = "" + val touchType = e!!.getToolType(0) + + when (touchType) { + MotionEvent.TOOL_TYPE_FINGER -> touchTypeDescription += "(finger)" + MotionEvent.TOOL_TYPE_STYLUS -> { + touchTypeDescription += "(stylus, " + //Get some additional information about the stylus touch + val stylusPressure = e.pressure + touchTypeDescription += "pressure: $stylusPressure" + + if (VERSION.SDK_INT >= 21) { + touchTypeDescription += ", buttons pressed: " + getButtonsPressed(e) + } + + touchTypeDescription += ")" + } + + MotionEvent.TOOL_TYPE_ERASER -> touchTypeDescription += "(eraser)" + MotionEvent.TOOL_TYPE_MOUSE -> touchTypeDescription += "(mouse)" + else -> touchTypeDescription += "(unknown tool)" + } + + return touchTypeDescription + } + + /** + * Returns a human-readable string listing all the stylus buttons that were pressed when the + * input MotionEvent occurred. + */ + private fun getButtonsPressed(e: MotionEvent?): String { + var buttons = "" + if (e == null) return "" + + if (e.isButtonPressed(MotionEvent.BUTTON_PRIMARY)) { + buttons += " primary" + } + + if (e.isButtonPressed(MotionEvent.BUTTON_SECONDARY)) { + buttons += " secondary" + } + + if (e.isButtonPressed(MotionEvent.BUTTON_TERTIARY)) { + buttons += " tertiary" + } + + if (e.isButtonPressed(MotionEvent.BUTTON_BACK)) { + buttons += " back" + } + + if (e.isButtonPressed(MotionEvent.BUTTON_FORWARD)) { + buttons += " forward" + } + + if (buttons == "") { + buttons = "none" + } + + return buttons + } + } + + override fun onSingleTapUp(e: MotionEvent): Boolean { + // Up motion completing a single tap occurred. + Log.i(TAG, "Single Tap Up" + getTouchType(e)) + return false + } + + override fun onLongPress(e: MotionEvent) { + // Touch has been long enough to indicate a long press. + // Does not indicate motion is complete yet (no up event necessarily) + Log.i(TAG, "Long Press" + getTouchType(e)) + } + + override fun onScroll( + e1: MotionEvent?, e2: MotionEvent, distanceX: Float, + distanceY: Float + ): Boolean { + // User attempted to scroll + Log.i(TAG, "Scroll" + getTouchType(e1)) + return false + } + + override fun onFling( + e1: MotionEvent?, e2: MotionEvent, velocityX: Float, + velocityY: Float + ): Boolean { + // Fling event occurred. Notification of this one happens after an "up" event. + Log.i(TAG, "Fling" + getTouchType(e1)) + return false + } + + override fun onShowPress(e: MotionEvent) { + // User performed a down event, and hasn't moved yet. + Log.i(TAG, "Show Press" + getTouchType(e)) + } + + override fun onDown(e: MotionEvent): Boolean { + // "Down" event - User touched the screen. + Log.i(TAG, "Down" + getTouchType(e)) + return false + } + + override fun onDoubleTap(e: MotionEvent): Boolean { + // User tapped the screen twice. + Log.i(TAG, "Double tap" + getTouchType(e)) + return false + } + + override fun onDoubleTapEvent(e: MotionEvent): Boolean { + // Since double-tap is actually several events which are considered one aggregate + // gesture, there's a separate callback for an individual event within the doubletap + // occurring. This occurs for down, up, and move. + Log.i(TAG, "Event within double tap" + getTouchType(e)) + return false + } + + override fun onSingleTapConfirmed(e: MotionEvent): Boolean { + // A confirmed single-tap event has occurred. Only called when the detector has + // determined that the first tap stands alone, and is not part of a double tap. + Log.i(TAG, "Single tap confirmed" + getTouchType(e)) + return false + } +} \ No newline at end of file diff --git a/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/views/recyclerview/CustomAdapter.kt b/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/views/recyclerview/CustomAdapter.kt index 9a2c944..304b502 100644 --- a/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/views/recyclerview/CustomAdapter.kt +++ b/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/views/recyclerview/CustomAdapter.kt @@ -22,21 +22,17 @@ import android.view.ViewGroup import android.widget.TextView import androidx.recyclerview.widget.RecyclerView import com.wintmain.foundation.R -import com.wintmain.foundation.views.recyclerview.logger.Log +import lib.wintmain.foundation.logger.Log -class CustomAdapter(private val dataSet: Array) : +class CustomAdapter(private val dataSet: Array) : RecyclerView.Adapter() { /** * Provide a reference to the type of views that you are using * (custom ViewHolder) */ class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { - val textView: TextView - - init { - // Define click listener for the ViewHolder's View - textView = view.findViewById(R.id.textView) - } + // Define click listener for the ViewHolder's View + val textView: TextView = view.findViewById(R.id.textView) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { diff --git a/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/views/recyclerview/RecyclerViewActivity.java b/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/views/recyclerview/RecyclerViewActivity.java deleted file mode 100644 index a2fb141..0000000 --- a/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/views/recyclerview/RecyclerViewActivity.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2023-2024 wintmain - * - * 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 - * - * https://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.wintmain.foundation.views.recyclerview; - -import android.os.Bundle; -import android.view.Menu; -import android.view.MenuItem; -import android.widget.ViewAnimator; -import androidx.appcompat.app.AppCompatActivity; -import androidx.fragment.app.FragmentTransaction; -import com.google.android.catalog.framework.annotations.Sample; -import com.wintmain.foundation.R; -import com.wintmain.foundation.views.recyclerview.logger.Log; -import com.wintmain.foundation.views.recyclerview.logger.LogFragment; -import com.wintmain.foundation.views.recyclerview.logger.LogWrapper; -import com.wintmain.foundation.views.recyclerview.logger.MessageOnlyLogFilter; - -import java.util.Objects; - -/** - * A simple launcher activity containing a summary sample description, sample log and a custom - * {@link androidx.fragment.app.Fragment} which can display a view. - */ -@Sample(name = "RecyclerView", description = "使用 RecyclerView 创建动态列表", - tags = {"android-samples", "recyclerView"}) -public class RecyclerViewActivity extends AppCompatActivity { - - public static final String TAG = "RecyclerViewActivity"; - - private boolean mLogShown; // Whether the Log Fragment is currently shown - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.recyclerview_activity_main); - - if (savedInstanceState == null) { - FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); - RecyclerViewFragment fragment = new RecyclerViewFragment(); - transaction.replace(R.id.sample_content_fragment, fragment); - transaction.commit(); - } - } - - @Override - protected void onStart() { - super.onStart(); - initializeLogging(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.recycler_main, menu); - return true; - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - MenuItem logToggle = menu.findItem(R.id.menu_toggle_log); - logToggle.setVisible(true); - logToggle.setTitle(mLogShown ? "sample_hide_log" : "sample_show_log"); - - return super.onPrepareOptionsMenu(menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == R.id.menu_toggle_log) { - mLogShown = !mLogShown; - ViewAnimator output = findViewById(R.id.sample_output); - if (mLogShown) { - output.setDisplayedChild(1); - } else { - output.setDisplayedChild(0); - } - supportInvalidateOptionsMenu(); - return true; - } - return super.onOptionsItemSelected(item); - } - - /** - * Create a chain of targets that will receive log data - */ - public void initializeLogging() { - // Wraps Android's native log framework. - LogWrapper logWrapper = new LogWrapper(); - // Using Log, front-end to the logging chain, emulates android.util.log method signatures. - Log.setLogNode(logWrapper); - - // Filter strips out everything except the message text. - MessageOnlyLogFilter msgFilter = new MessageOnlyLogFilter(); - logWrapper.setNext(msgFilter); - - // On screen logging via a fragment with a TextView. - LogFragment logFragment = (LogFragment) getSupportFragmentManager() - .findFragmentById(R.id.log_fragment); - msgFilter.setNext(Objects.requireNonNull(logFragment).getLogView()); - - Log.i(TAG, "Ready...RecyclerViewActivity"); - } -} diff --git a/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/views/recyclerview/RecyclerViewActivity.kt b/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/views/recyclerview/RecyclerViewActivity.kt new file mode 100644 index 0000000..f86d163 --- /dev/null +++ b/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/views/recyclerview/RecyclerViewActivity.kt @@ -0,0 +1,111 @@ +/* + * Copyright 2023-2024 wintmain + * + * 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 + * + * https://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.wintmain.foundation.views.recyclerview + +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.widget.ViewAnimator +import androidx.appcompat.app.AppCompatActivity +import com.google.android.catalog.framework.annotations.Sample +import com.wintmain.foundation.R +import lib.wintmain.foundation.logger.Log +import lib.wintmain.foundation.logger.LogFragment +import lib.wintmain.foundation.logger.LogWrapper +import lib.wintmain.foundation.logger.MessageOnlyLogFilter + +/** + * A simple launcher activity containing a summary sample description, sample log and a custom + * [androidx.fragment.app.Fragment] which can display a view. + */ +@Sample( + name = "RecyclerView", + description = "使用 RecyclerView 创建动态列表", + tags = ["android-samples", "user-interface"] +) +class RecyclerViewActivity : AppCompatActivity() { + companion object { + const val TAG: String = "RecyclerViewActivity" + } + + private var mLogShown = false // Whether the Log Fragment is currently shown + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.recyclerview_activity_main) + + if (savedInstanceState == null) { + val transaction = supportFragmentManager.beginTransaction() + val fragment = RecyclerViewFragment() + transaction.replace(R.id.sample_content_fragment, fragment).commit() + } + } + + override fun onStart() { + super.onStart() + initializeLogging() + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.recycler_main, menu) + return true + } + + override fun onPrepareOptionsMenu(menu: Menu): Boolean { + menu.findItem(R.id.menu_toggle_log).apply { + isVisible = true + title = if (mLogShown) "sample_hide_log" else "sample_show_log" + } + + return super.onPrepareOptionsMenu(menu) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + if (item.itemId == R.id.menu_toggle_log) { + mLogShown = !mLogShown + val output = findViewById(R.id.sample_output) + if (mLogShown) { + output.displayedChild = 1 + } else { + output.displayedChild = 0 + } + supportInvalidateOptionsMenu() + return true + } + return super.onOptionsItemSelected(item) + } + + /** + * Create a chain of targets that will receive log data + */ + private fun initializeLogging() { + // Wraps Android's native log framework. + val logWrapper = LogWrapper() + // Using Log, front-end to the logging chain, emulates android.util.log method signatures. + Log.setLogNode(logWrapper) + + // Filter strips out everything except the message text. + val msgFilter = MessageOnlyLogFilter() + logWrapper.next = msgFilter + + // On screen logging via a fragment with a TextView. + val logFragment = supportFragmentManager + .findFragmentById(R.id.log_fragment) as LogFragment + msgFilter.next = logFragment.logView + + Log.i(TAG, "Ready...RecyclerViewActivity") + } +} \ No newline at end of file diff --git a/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/views/recyclerview/RecyclerViewFragment.java b/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/views/recyclerview/RecyclerViewFragment.java deleted file mode 100644 index 1c5bff0..0000000 --- a/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/views/recyclerview/RecyclerViewFragment.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2023-2024 wintmain - * - * 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 - * - * https://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.wintmain.foundation.views.recyclerview; - -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.RadioButton; -import androidx.fragment.app.Fragment; -import androidx.recyclerview.widget.GridLayoutManager; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import com.wintmain.foundation.R; - -import java.util.Objects; - -/** - * Demonstrates the use of {@link RecyclerView} with a {@link LinearLayoutManager} and a - * {@link GridLayoutManager}. - */ -public class RecyclerViewFragment extends Fragment { - - private static final String TAG = "RecyclerViewFragment"; - private static final String KEY_LAYOUT_MANAGER = "layoutManager"; - private static final int SPAN_COUNT = 2; - private static final int DATASET_COUNT = 100; - protected LayoutManagerType mCurrentLayoutManagerType; - protected RadioButton mLinearLayoutRadioButton; - protected RadioButton mGridLayoutRadioButton; - protected RecyclerView mRecyclerView; - protected CustomAdapter mAdapter; - protected RecyclerView.LayoutManager mLayoutManager; - protected String[] mDataset; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - // Initialize dataset, this data would usually come from a local content provider or - // remote server. - // Generates Strings for RecyclerView's adapter. - mDataset = new String[DATASET_COUNT]; - for (int i = 0; i < DATASET_COUNT; i++) { - mDataset[i] = "Element #" + i; - } - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View rootView = inflater.inflate(R.layout.recycler_view_frag, container, false); - rootView.setTag(TAG); - - mRecyclerView = rootView.findViewById(R.id.recyclerView); - - // LinearLayoutManager is used here, this will layout the elements in a similar fashion - // to the way ListView would layout elements. The RecyclerView.LayoutManager defines how - // elements are laid out. - mLayoutManager = new LinearLayoutManager(getActivity()); - - mCurrentLayoutManagerType = LayoutManagerType.LINEAR_LAYOUT_MANAGER; - - if (savedInstanceState != null) { - // Restore saved layout manager type. - mCurrentLayoutManagerType = (LayoutManagerType) savedInstanceState.getSerializable( - KEY_LAYOUT_MANAGER); - } - setRecyclerViewLayoutManager(mCurrentLayoutManagerType); - - mAdapter = new CustomAdapter(mDataset); - // Set CustomAdapter as the adapter for RecyclerView. - mRecyclerView.setAdapter(mAdapter); - - mLinearLayoutRadioButton = rootView.findViewById(R.id.linear_layout_rb); - mLinearLayoutRadioButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - setRecyclerViewLayoutManager(LayoutManagerType.LINEAR_LAYOUT_MANAGER); - } - }); - - mGridLayoutRadioButton = rootView.findViewById(R.id.grid_layout_rb); - mGridLayoutRadioButton.setOnClickListener(v -> - setRecyclerViewLayoutManager(LayoutManagerType.GRID_LAYOUT_MANAGER)); - - return rootView; - } - - /** - * Set RecyclerView's LayoutManager to the one given. - * - * @param layoutManagerType Type of layout manager to switch to. - */ - public void setRecyclerViewLayoutManager(LayoutManagerType layoutManagerType) { - int scrollPosition = 0; - - // If a layout manager has already been set, get current scroll position. - if (mRecyclerView.getLayoutManager() != null) { - // java.lang.ClassCastException: - // androidx.recyclerview.widget.StaggeredGridLayoutManager cannot be cast to - // androidx.recyclerview.widget.LinearLayoutManager - scrollPosition = ((LinearLayoutManager) mRecyclerView.getLayoutManager()) - .findFirstCompletelyVisibleItemPosition(); - } - - if (Objects.requireNonNull(layoutManagerType) == LayoutManagerType.GRID_LAYOUT_MANAGER) { - mLayoutManager = new GridLayoutManager(getActivity(), SPAN_COUNT); - mCurrentLayoutManagerType = LayoutManagerType.GRID_LAYOUT_MANAGER; - } else { - mLayoutManager = new LinearLayoutManager(getActivity()); - mCurrentLayoutManagerType = LayoutManagerType.LINEAR_LAYOUT_MANAGER; - } - - mRecyclerView.setLayoutManager(mLayoutManager); - mRecyclerView.scrollToPosition(scrollPosition); - } - - @Override - public void onSaveInstanceState(Bundle savedInstanceState) { - // Save currently selected layout manager. - savedInstanceState.putSerializable(KEY_LAYOUT_MANAGER, mCurrentLayoutManagerType); - super.onSaveInstanceState(savedInstanceState); - } - - private enum LayoutManagerType { - GRID_LAYOUT_MANAGER, - LINEAR_LAYOUT_MANAGER - } -} diff --git a/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/views/recyclerview/RecyclerViewFragment.kt b/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/views/recyclerview/RecyclerViewFragment.kt new file mode 100644 index 0000000..40cc6ea --- /dev/null +++ b/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/views/recyclerview/RecyclerViewFragment.kt @@ -0,0 +1,149 @@ +/* + * Copyright 2023-2024 wintmain + * + * 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 + * + * https://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.wintmain.foundation.views.recyclerview + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.RadioButton +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.LayoutManager +import com.wintmain.foundation.R +import com.wintmain.foundation.views.recyclerview.RecyclerViewFragment.LayoutManagerType.GRID_LAYOUT_MANAGER +import com.wintmain.foundation.views.recyclerview.RecyclerViewFragment.LayoutManagerType.LINEAR_LAYOUT_MANAGER +import java.util.Objects + +/** + * Demonstrates the use of [RecyclerView] with a [LinearLayoutManager] and a + * [GridLayoutManager]. + */ +class RecyclerViewFragment : Fragment() { + companion object { + private const val TAG = "RecyclerViewFragment" + private const val KEY_LAYOUT_MANAGER = "layoutManager" + private const val SPAN_COUNT = 2 + private const val DATASET_COUNT = 100 + } + + private lateinit var mCurrentLayoutManagerType: LayoutManagerType + private lateinit var mLinearLayoutRadioButton: RadioButton + private lateinit var mGridLayoutRadioButton: RadioButton + private lateinit var mRecyclerView: RecyclerView + private lateinit var mAdapter: CustomAdapter + private lateinit var mLayoutManager: LayoutManager + private lateinit var mDataset: Array + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // Initialize dataset, this data would usually come from a local content provider or + // remote server. + // Generates Strings for RecyclerView's adapter. + mDataset = arrayOfNulls(DATASET_COUNT) + for (i in 0 until DATASET_COUNT) { + mDataset[i] = "Element #$i" + } + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + val rootView = inflater.inflate(R.layout.recycler_view_frag, container, false) + rootView.tag = TAG + + mRecyclerView = rootView.findViewById(R.id.recyclerView) + + // LinearLayoutManager is used here, this will layout the elements in a similar fashion + // to the way ListView would layout elements. The RecyclerView.LayoutManager defines how + // elements are laid out. + mLayoutManager = LinearLayoutManager(activity) + + mCurrentLayoutManagerType = LINEAR_LAYOUT_MANAGER + + if (savedInstanceState != null) { + // Restore saved layout manager type. + mCurrentLayoutManagerType = (savedInstanceState.getSerializable( + KEY_LAYOUT_MANAGER + ) as LayoutManagerType?)!! + } + setRecyclerViewLayoutManager(mCurrentLayoutManagerType) + + mAdapter = CustomAdapter(mDataset) + // Set CustomAdapter as the adapter for RecyclerView. + mRecyclerView.setAdapter(mAdapter) + + mLinearLayoutRadioButton = rootView.findViewById(R.id.linear_layout_rb) + mLinearLayoutRadioButton.setOnClickListener { + setRecyclerViewLayoutManager( + LINEAR_LAYOUT_MANAGER + ) + } + + mGridLayoutRadioButton = rootView.findViewById(R.id.grid_layout_rb) + mGridLayoutRadioButton.setOnClickListener { + setRecyclerViewLayoutManager( + GRID_LAYOUT_MANAGER + ) + } + + return rootView + } + + /** + * Set RecyclerView's LayoutManager to the one given. + * + * @param layoutManagerType Type of layout manager to switch to. + */ + private fun setRecyclerViewLayoutManager(layoutManagerType: LayoutManagerType?) { + var scrollPosition = 0 + + // If a layout manager has already been set, get current scroll position. + if (mRecyclerView.layoutManager != null) { + // java.lang.ClassCastException: + // androidx.recyclerview.widget.StaggeredGridLayoutManager cannot be cast to + // androidx.recyclerview.widget.LinearLayoutManager + scrollPosition = (mRecyclerView.layoutManager as LinearLayoutManager) + .findFirstCompletelyVisibleItemPosition() + } + + if (Objects.requireNonNull(layoutManagerType) == GRID_LAYOUT_MANAGER) { + mLayoutManager = GridLayoutManager(activity, SPAN_COUNT) + mCurrentLayoutManagerType = GRID_LAYOUT_MANAGER + } else { + mLayoutManager = LinearLayoutManager(activity) + mCurrentLayoutManagerType = LINEAR_LAYOUT_MANAGER + } + + mRecyclerView.layoutManager = mLayoutManager + mRecyclerView.scrollToPosition(scrollPosition) + } + + override fun onSaveInstanceState(savedInstanceState: Bundle) { + // Save currently selected layout manager. + savedInstanceState.putSerializable(KEY_LAYOUT_MANAGER, mCurrentLayoutManagerType) + super.onSaveInstanceState(savedInstanceState) + } + + enum class LayoutManagerType { + GRID_LAYOUT_MANAGER, + LINEAR_LAYOUT_MANAGER + } +} diff --git a/app-catalog/samples/foundation/src/main/res-ext/layout/gesture_detect_main.xml b/app-catalog/samples/foundation/src/main/res-ext/layout/gesture_detect_main.xml new file mode 100644 index 0000000..b69c465 --- /dev/null +++ b/app-catalog/samples/foundation/src/main/res-ext/layout/gesture_detect_main.xml @@ -0,0 +1,41 @@ + + + + + + + \ No newline at end of file diff --git a/app-catalog/samples/foundation/src/main/res-ext/layout/pft_activity_main.xml b/app-catalog/samples/foundation/src/main/res-ext/layout/pft_activity_main.xml index 2273584..5ed2dc9 100644 --- a/app-catalog/samples/foundation/src/main/res-ext/layout/pft_activity_main.xml +++ b/app-catalog/samples/foundation/src/main/res-ext/layout/pft_activity_main.xml @@ -38,13 +38,13 @@ android:paddingBottom="20dp" android:paddingTop="20dp"> - diff --git a/app-catalog/samples/foundation/src/main/res-ext/layout/recyclerview_activity_main.xml b/app-catalog/samples/foundation/src/main/res-ext/layout/recyclerview_activity_main.xml index d6b5c31..5080eba 100644 --- a/app-catalog/samples/foundation/src/main/res-ext/layout/recyclerview_activity_main.xml +++ b/app-catalog/samples/foundation/src/main/res-ext/layout/recyclerview_activity_main.xml @@ -43,7 +43,7 @@ diff --git a/app-catalog/samples/foundation/src/main/res-ext/menu/gesture_main.xml b/app-catalog/samples/foundation/src/main/res-ext/menu/gesture_main.xml new file mode 100644 index 0000000..d1b049f --- /dev/null +++ b/app-catalog/samples/foundation/src/main/res-ext/menu/gesture_main.xml @@ -0,0 +1,24 @@ + + + + + + \ No newline at end of file diff --git a/app-catalog/samples/foundation/src/main/res-ext/values/strings.xml b/app-catalog/samples/foundation/src/main/res-ext/values/strings.xml index 19010cb..a80a823 100644 --- a/app-catalog/samples/foundation/src/main/res-ext/values/strings.xml +++ b/app-catalog/samples/foundation/src/main/res-ext/values/strings.xml @@ -24,6 +24,16 @@ HttpsURLConnection. AsyncTask is used to perform the fetch on a background thread. + ]]> + + + diff --git a/app-catalog/samples/foundation/src/main/res-ext/values/styles.xml b/app-catalog/samples/foundation/src/main/res-ext/values/styles.xml index de204de..d2a1456 100644 --- a/app-catalog/samples/foundation/src/main/res-ext/values/styles.xml +++ b/app-catalog/samples/foundation/src/main/res-ext/values/styles.xml @@ -39,4 +39,14 @@ 2 + + + + \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 35a3d1a..8218668 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,6 +17,7 @@ # Modifications are licensed under the License. [versions] +coil = "2.6.0" compose-bom = "2024.09.02" composeCompiler = "1.5.9" kotlin = "1.9.22" @@ -38,6 +39,7 @@ casa-ui = { module = "com.google.android.catalog.framework:casa-ui", version.ref casa-base = { module = "com.google.android.catalog.framework:casa-base", version.ref = "casa" } casa-processor = { module = "com.google.android.catalog.framework:casa-processor", version.ref = "casa" } +coil = { module = "io.coil-kt:coil", version.ref = "coil" } hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" } hilt-core = { module = "com.google.dagger:hilt-core", version.ref = "hilt" } hilt-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hilt" }