Bladeren bron

code cleanup & improvements

Milan Jurkulak 3 jaren geleden
bovenliggende
commit
edf253dd7a

+ 0 - 1
README.md

@@ -62,7 +62,6 @@ Technologies that will be used:
 - Solve problems with compose previews
 - Coroutines
 - Flow
-- Paging
 - Navigation
 - Retrofit
 - Json serialization (Moshi)

+ 5 - 3
app/build.gradle.kts

@@ -13,6 +13,8 @@ import org.mjdev.gradle.extensions.kapt
 import org.mjdev.gradle.plugin.MainAppPlugin.Companion.javaVersion
 import org.mjdev.gradle.plugin.MainAppPlugin.Companion.kotlinCompilerExtVersion
 import org.mjdev.gradle.plugin.MainAppPlugin.Companion.loadKeyStoreProperties
+import org.mjdev.gradle.plugin.MainAppPlugin.Companion.projectCompileSdk
+import org.mjdev.gradle.plugin.MainAppPlugin.Companion.projectMinSdk
 import org.mjdev.gradle.plugin.MainAppPlugin.Companion.versionCode
 import org.mjdev.gradle.plugin.MainAppPlugin.Companion.versionName
 
@@ -37,7 +39,7 @@ plugins {
 
 android {
     namespace = "org.mjdev.tvapp"
-    compileSdk = 34
+    compileSdk = projectCompileSdk
 
     signingConfigs {
         loadKeyStoreProperties(
@@ -55,8 +57,8 @@ android {
     defaultConfig {
         applicationId = "org.mjdev.tvapp"
 
-        minSdk = 21
-        targetSdk = 34
+        minSdk = projectMinSdk
+        targetSdk = projectCompileSdk
 
         versionCode = project.versionCode
         versionName = project.versionName

+ 4 - 3
buildSrc/src/main/kotlin/org/mjdev/gradle/plugin/MainAppPlugin.kt

@@ -23,7 +23,7 @@ import java.io.FileInputStream
 import java.util.Properties
 
 // todo
-@Suppress("UsePropertyAccessSyntax", "UnusedReceiverParameter")
+@Suppress("UsePropertyAccessSyntax")
 abstract class MainAppPlugin : Plugin<Project> {
 
     companion object {
@@ -62,6 +62,9 @@ abstract class MainAppPlugin : Plugin<Project> {
 
         val javaVersion: JavaVersion = JavaVersion.VERSION_17
 
+        const val projectCompileSdk: Int = 34
+        const val projectMinSdk = 21
+
         const val kotlinCompilerExtVersion = "1.5.1"
 
         inline fun <reified T : Task> Project.registerTask(
@@ -117,12 +120,10 @@ abstract class MainAppPlugin : Plugin<Project> {
 //            implementation("androidx.compose.ui:ui-graphics:1.4.3"),
 //            implementation("androidx.compose.ui:ui-tooling-preview:1.4.3"),
 //            implementation("androidx.lifecycle:lifecycle-runtime-compose:2.6.1"),
-//            implementation("androidx.paging:paging-compose:1.0.0-alpha20"),
 //            implementation("androidx.compose.material3:material3:1.1.0"),
 //            implementation("androidx.compose.material3:material3-window-size-class:1.1.0"),
 //            implementation("androidx.hilt:hilt-navigation-compose:1.0.0"),
 //            implementation("androidx.navigation:navigation-compose:2.5.3"),
-//            implementation("androidx.paging:paging-compose:1.0.0-alpha20"),
 //            // kotlin conf
 //            constraints {
 //                implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.0")?.apply {

+ 8 - 7
tvlib/build.gradle.kts

@@ -1,6 +1,9 @@
 @file:Suppress("UnstableApiUsage")
 
+import org.mjdev.gradle.plugin.MainAppPlugin.Companion.javaVersion
 import org.mjdev.gradle.plugin.MainAppPlugin.Companion.kotlinCompilerExtVersion
+import org.mjdev.gradle.plugin.MainAppPlugin.Companion.projectCompileSdk
+import org.mjdev.gradle.plugin.MainAppPlugin.Companion.projectMinSdk
 
 /*
  * Copyright (c) Milan Jurkulák 2023.
@@ -22,10 +25,10 @@ plugins {
 
 android {
     namespace = "org.mjdev.tvlib"
-    compileSdk = 34
+    compileSdk = projectCompileSdk
 
     defaultConfig {
-        minSdk = 21
+        minSdk = projectMinSdk
         testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
         consumerProguardFiles("consumer-rules.pro")
     }
@@ -41,12 +44,12 @@ android {
     }
 
     compileOptions {
-        sourceCompatibility = JavaVersion.VERSION_17
-        targetCompatibility = JavaVersion.VERSION_17
+        sourceCompatibility = javaVersion
+        targetCompatibility = javaVersion
     }
 
     kotlinOptions {
-        jvmTarget = JavaVersion.VERSION_17.toString()
+        jvmTarget = javaVersion.toString()
     }
 
     buildFeatures {
@@ -94,8 +97,6 @@ dependencies {
     // tv compose
     implementation("androidx.tv:tv-foundation:1.0.0-alpha08")
     implementation("androidx.tv:tv-material:1.0.0-alpha08")
-    // paging
-    implementation("androidx.paging:paging-compose:3.2.0")
     // view model
     implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1")
     // navigation

+ 6 - 6
tvlib/src/main/kotlin/org/mjdev/tvlib/extensions/ComposeExt.kt

@@ -154,12 +154,12 @@ object ComposeExt {
                     OkHttpClient.Builder()
                         .cache(
                             Cache(
-                            directory = File(
-                                context.applicationContext.cacheDir,
-                                "http_cache"
-                            ),
-                            maxSize = 1024L * 1024L * 1024L
-                        )
+                                directory = File(
+                                    context.applicationContext.cacheDir,
+                                    "http_cache"
+                                ),
+                                maxSize = 1024L * 1024L * 1024L
+                            )
                         )
                         .addNetworkInterceptor(HttpLoggingInterceptor().apply {
                             level = HttpLoggingInterceptor.Level.BODY

+ 1 - 0
tvlib/src/main/kotlin/org/mjdev/tvlib/extensions/ListExt.kt

@@ -10,6 +10,7 @@ package org.mjdev.tvlib.extensions
 
 import androidx.core.os.bundleOf
 
+@Suppress("unused")
 object ListExt {
 
     fun <T> MutableList<T>.addUnique(element: T, replace: Boolean = true) {

+ 3 - 0
tvlib/src/main/kotlin/org/mjdev/tvlib/helpers/cursor/CachingCursor.kt

@@ -47,13 +47,16 @@ open class CachingCursor(
     private val resolver: ContentResolver by lazy {
         context.contentResolver
     }
+
     private val observer = object : ContentObserver(null) {
         @Suppress("DEPRECATION")
         override fun onChange(selfChange: Boolean) {
             requery()
         }
     }
+
     private val cache: MutableList<CachedCursorItem> = mutableListOf()
+
     private val cursor: Cursor? by lazy {
         runBlocking(Dispatchers.IO) {
             if (uri != null) {

+ 237 - 0
tvlib/src/main/kotlin/org/mjdev/tvlib/helpers/cursor/MatrixCursor.java

@@ -0,0 +1,237 @@
+package org.mjdev.tvlib.helpers.cursor;
+
+import android.database.AbstractCursor;
+import android.database.Cursor;
+import android.database.CursorIndexOutOfBoundsException;
+
+import java.util.ArrayList;
+
+public class MatrixCursor extends AbstractCursor {
+
+    private final String[] columnNames;
+    private Object[] data;
+    private int rowCount = 0;
+    private final int columnCount;
+
+    public MatrixCursor(String[] columnNames, int initialCapacity) {
+        this.columnNames = columnNames;
+        this.columnCount = columnNames.length;
+        if (initialCapacity < 1) {
+            initialCapacity = 1;
+        }
+        this.data = new Object[columnCount * initialCapacity];
+    }
+
+    public MatrixCursor(String[] columnNames) {
+        this(columnNames, 16);
+    }
+
+    private Object get(int column) {
+        if (column < 0 || column >= columnCount) {
+            throw new CursorIndexOutOfBoundsException("Requested column: "
+                    + column + ", # of columns: " + columnCount);
+        }
+        if (mPos < 0) {
+            throw new CursorIndexOutOfBoundsException("Before first row.");
+        }
+        if (mPos >= rowCount) {
+            throw new CursorIndexOutOfBoundsException("After last row.");
+        }
+        return data[mPos * columnCount + column];
+    }
+
+    public void clear() {
+        data = new Object[0];
+    }
+
+    public RowBuilder newRow() {
+        final int row = rowCount++;
+        final int endIndex = rowCount * columnCount;
+        ensureCapacity(endIndex);
+        return new RowBuilder(row);
+    }
+
+    public void addRow(Object[] columnValues) {
+        if (columnValues.length != columnCount) {
+            throw new IllegalArgumentException("columnNames.length = "
+                    + columnCount + ", columnValues.length = "
+                    + columnValues.length);
+        }
+        int start = rowCount++ * columnCount;
+        ensureCapacity(start + columnCount);
+        System.arraycopy(columnValues, 0, data, start, columnCount);
+    }
+
+    public void addRow(Iterable<?> columnValues) {
+        int start = rowCount * columnCount;
+        int end = start + columnCount;
+        ensureCapacity(end);
+        if (columnValues instanceof ArrayList<?>) {
+            addRow((ArrayList<?>) columnValues, start);
+            return;
+        }
+        int current = start;
+        Object[] localData = data;
+        for (Object columnValue : columnValues) {
+            if (current == end) {
+                throw new IllegalArgumentException(
+                        "columnValues.size() > columnNames.length");
+            }
+            localData[current++] = columnValue;
+        }
+        if (current != end) {
+            throw new IllegalArgumentException(
+                    "columnValues.size() < columnNames.length");
+        }
+        rowCount++;
+    }
+
+    private void addRow(ArrayList<?> columnValues, int start) {
+        int size = columnValues.size();
+        if (size != columnCount) {
+            throw new IllegalArgumentException("columnNames.length = "
+                    + columnCount + ", columnValues.size() = " + size);
+        }
+        rowCount++;
+        Object[] localData = data;
+        for (int i = 0; i < size; i++) {
+            localData[start + i] = columnValues.get(i);
+        }
+    }
+
+    private void ensureCapacity(int size) {
+        if (size > data.length) {
+            Object[] oldData = this.data;
+            int newSize = data.length * 2;
+            if (newSize < size) {
+                newSize = size;
+            }
+            this.data = new Object[newSize];
+            System.arraycopy(oldData, 0, this.data, 0, oldData.length);
+        }
+    }
+
+    public class RowBuilder {
+        private final int row;
+        private final int endIndex;
+
+        private int index;
+
+        RowBuilder(int row) {
+            this.row = row;
+            this.index = row * columnCount;
+            this.endIndex = index + columnCount;
+        }
+
+        public RowBuilder add(Object columnValue) {
+            if (index == endIndex) {
+                throw new CursorIndexOutOfBoundsException("No more columns left.");
+            }
+            data[index++] = columnValue;
+            return this;
+        }
+
+        public RowBuilder add(String columnName, Object value) {
+            for (int i = 0; i < columnNames.length; i++) {
+                if (columnName.equals(columnNames[i])) {
+                    data[(row * columnCount) + i] = value;
+                }
+            }
+            return this;
+        }
+
+        public final RowBuilder add(int columnIndex, Object value) {
+            data[(row * columnCount) + columnIndex] = value;
+            return this;
+        }
+    }
+
+    @Override
+    public int getCount() {
+        return rowCount;
+    }
+
+    @Override
+    public String[] getColumnNames() {
+        return columnNames;
+    }
+
+    @Override
+    public String getString(int column) {
+        Object value = get(column);
+        if (value == null) return null;
+        return value.toString();
+    }
+
+    @Override
+    public short getShort(int column) {
+        Object value = get(column);
+        if (value == null) return 0;
+        if (value instanceof Number) return ((Number) value).shortValue();
+        return Short.parseShort(value.toString());
+    }
+
+    @Override
+    public int getInt(int column) {
+        Object value = get(column);
+        if (value == null) return 0;
+        if (value instanceof Number) return ((Number) value).intValue();
+        return Integer.parseInt(value.toString());
+    }
+
+    @Override
+    public long getLong(int column) {
+        Object value = get(column);
+        if (value == null) return 0;
+        if (value instanceof Number) return ((Number) value).longValue();
+        return Long.parseLong(value.toString());
+    }
+
+    @Override
+    public float getFloat(int column) {
+        Object value = get(column);
+        if (value == null) return 0.0f;
+        if (value instanceof Number) return ((Number) value).floatValue();
+        return Float.parseFloat(value.toString());
+    }
+
+    @Override
+    public double getDouble(int column) {
+        Object value = get(column);
+        if (value == null) return 0.0d;
+        if (value instanceof Number) return ((Number) value).doubleValue();
+        return Double.parseDouble(value.toString());
+    }
+
+    @Override
+    public byte[] getBlob(int column) {
+        Object value = get(column);
+        return (byte[]) value;
+    }
+
+    @Override
+    public int getType(int column) {
+        return getTypeOfObject(get(column));
+    }
+
+    @Override
+    public boolean isNull(int column) {
+        return get(column) == null;
+    }
+
+    public static int getTypeOfObject(Object obj) {
+        if (obj == null) {
+            return Cursor.FIELD_TYPE_NULL;
+        } else if (obj instanceof byte[]) {
+            return Cursor.FIELD_TYPE_BLOB;
+        } else if (obj instanceof Float || obj instanceof Double) {
+            return Cursor.FIELD_TYPE_FLOAT;
+        } else if (obj instanceof Long || obj instanceof Integer
+                || obj instanceof Short || obj instanceof Byte) {
+            return Cursor.FIELD_TYPE_INTEGER;
+        } else {
+            return Cursor.FIELD_TYPE_STRING;
+        }
+    }
+
+}

+ 0 - 70
tvlib/src/main/kotlin/org/mjdev/tvlib/paging/ListPagingSource.kt

@@ -1,70 +0,0 @@
-/*
- * Copyright (c) Milan Jurkulák 2023.
- *  Contact:
- *  e: mimoccc@gmail.com
- *  e: mj@mjdev.org
- *  w: https://mjdev.org
- */
-
-package org.mjdev.tvlib.paging
-
-import androidx.paging.PagingSource
-import androidx.paging.PagingState
-import kotlinx.coroutines.delay
-
-typealias SOURCE<T> = suspend (page: Int, cnt: Int) -> List<T>
-
-class ListPagingSource<T : Any>(
-    private val source: SOURCE<T> = { _, _ -> emptyList() },
-    private val maxRetryCount: Int = DEFAULT_MAX_RETRY_COUNT,
-    private val retryDelay: Long = DEFAULT_RETRY_DELAY,
-) : PagingSource<Int, T>() {
-
-    companion object {
-        const val DEFAULT_MAX_RETRY_COUNT = 10
-        const val DEFAULT_RETRY_DELAY = 250L // 250 ms
-    }
-
-    override fun getRefreshKey(state: PagingState<Int, T>): Int? {
-        return state.anchorPosition?.let { anchorPosition ->
-            state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
-                ?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
-        }
-    }
-
-    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, T> {
-        return try {
-            val page = params.key ?: 1
-            val response: List<T> = retrySuspend(
-                retryDelay = retryDelay,
-                maxRetryCount = maxRetryCount,
-                condition = { size >= params.loadSize },
-                block = { source.invoke(page, params.loadSize) }
-            )
-            return LoadResult.Page(
-                data = response,
-                prevKey = if (page == 1) null else page.minus(1),
-                nextKey = if (response.isEmpty()) null else page.plus(1),
-            )
-        } catch (e: Exception) {
-            LoadResult.Error(e)
-        }
-    }
-
-    private suspend fun <T> retrySuspend(
-        retryDelay: Long = DEFAULT_RETRY_DELAY,
-        maxRetryCount: Int = DEFAULT_MAX_RETRY_COUNT,
-        condition: T.() -> Boolean,
-        block: suspend () -> T
-    ): T {
-        var retryCount = maxRetryCount
-        var ret = block.invoke()
-        while ((!condition.invoke(ret)) && (retryCount > 0)) {
-            delay(retryDelay)
-            ret = block.invoke()
-            retryCount -= 1
-        }
-        return ret
-    }
-
-}

+ 0 - 98
tvlib/src/main/kotlin/org/mjdev/tvlib/ui/components/tv/PagingTvLazyRow.kt

@@ -1,98 +0,0 @@
-/*
- * Copyright (c) Milan Jurkulák 2023.
- *  Contact:
- *  e: mimoccc@gmail.com
- *  e: mj@mjdev.org
- *  w: https://mjdev.org
- */
-
-package org.mjdev.tvlib.ui.components.tv
-
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.ui.Alignment
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
-import androidx.paging.LoadState
-import androidx.paging.Pager
-import androidx.paging.PagingConfig
-import androidx.paging.compose.collectAsLazyPagingItems
-import androidx.paging.compose.itemContentType
-import androidx.paging.compose.itemKey
-import androidx.tv.foundation.PivotOffsets
-import androidx.tv.foundation.lazy.list.TvLazyListState
-import androidx.tv.foundation.lazy.list.TvLazyRow
-import androidx.tv.foundation.lazy.list.rememberTvLazyListState
-import org.mjdev.tvlib.annotations.TvPreview
-import org.mjdev.tvlib.extensions.ModifierExt.recomposeHighlighter
-import org.mjdev.tvlib.paging.ListPagingSource
-import org.mjdev.tvlib.paging.SOURCE
-import org.mjdev.tvlib.R
-import org.mjdev.tvlib.ui.components.text.TextAny
-
-@Suppress("ModifierParameter")
-@TvPreview
-@Composable
-fun <T : Any> PagingTvLazyRow(
-    modifier: Modifier = Modifier.fillMaxSize(),
-    state: TvLazyListState = rememberTvLazyListState(),
-    contentPadding: Dp = 0.dp,
-    reverseLayout: Boolean = false,
-    horizontalArrangement: Arrangement.Horizontal = if (!reverseLayout) Arrangement.Start
-    else Arrangement.End,
-    verticalAlignment: Alignment.Vertical = Alignment.Top,
-    userScrollEnabled: Boolean = true,
-    pivotOffsets: PivotOffsets = PivotOffsets(),
-    perPage: Int = 8,
-    source: SOURCE<T> = { _, _ -> emptyList() },
-    loadStateHandler: (state: LoadState) -> Unit = {},
-    onItemClick: (data: T) -> Unit = { _ -> },
-    emptyContent: @Composable () -> Unit = { TextAny(text = R.string.no_items) },
-    itemBlock: @Composable (
-        idx: Int, item: T, onItemClick: (data: T) -> Unit
-    ) -> Unit = { _, _, _ -> }
-) {
-
-    val listData = remember {
-        Pager(config = PagingConfig(
-            pageSize = perPage, prefetchDistance = perPage, initialLoadSize = perPage
-        ), pagingSourceFactory = {
-            ListPagingSource(source)
-        }).flow
-    }.collectAsLazyPagingItems().apply {
-        loadStateHandler.invoke(loadState.refresh)
-    }
-
-    TvLazyRow(
-        modifier = modifier.recomposeHighlighter(),
-        state = state,
-        contentPadding = PaddingValues(contentPadding),
-        reverseLayout = reverseLayout,
-        horizontalArrangement = horizontalArrangement,
-        verticalAlignment = verticalAlignment,
-        userScrollEnabled = userScrollEnabled,
-        pivotOffsets = pivotOffsets,
-    ) {
-        if (listData.itemCount == 0) {
-            item {
-                emptyContent()
-            }
-        } else {
-            items(
-                count = listData.itemCount,
-                key = listData.itemKey(),
-                contentType = listData.itemContentType()
-            ) { index ->
-                val item = listData[index]
-                if (item != null) {
-                    itemBlock(index, item, onItemClick)
-                }
-            }
-        }
-    }
-
-}

+ 1 - 2
wiki/Home.md

@@ -37,7 +37,6 @@ Just an test app to skills showcase of coding skills, using
 - Solve problems with compose previews
 - Coroutines
 - Flow
-- Paging
 - Navigation
 - Retrofit
 - Json serialization (Moshi)
@@ -79,4 +78,4 @@ Milan Jurkulak
 
 ## License
 
-No licenced.
+No licenced.