diff --git a/android/app/build.gradle b/android/app/build.gradle deleted file mode 100644 index 32c6fa3..0000000 --- a/android/app/build.gradle +++ /dev/null @@ -1,85 +0,0 @@ -plugins { - id 'com.android.application' - id 'org.jetbrains.kotlin.android' - id 'com.google.devtools.ksp' -} - -android { - namespace 'com.localstore' - compileSdk 34 - - defaultConfig { - applicationId "com.localstore" - minSdk 26 - targetSdk 34 - versionCode 1 - versionName "1.0" - } - - signingConfigs { - release { - storeFile file("${System.getenv('HOME')}/playground/app-store/signing/localstore.keystore") - storePassword "localstore123" - keyAlias "localstore" - keyPassword "localstore123" - } - } - - buildTypes { - release { - minifyEnabled false - signingConfig signingConfigs.release - } - debug { - signingConfig signingConfigs.debug - } - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 - } - - kotlinOptions { - jvmTarget = "17" - } - - buildFeatures { - viewBinding true - } - - lint { - checkReleaseBuilds false - } -} - -dependencies { - implementation 'androidx.core:core-ktx:1.13.1' - implementation 'androidx.appcompat:appcompat:1.7.0' - implementation 'com.google.android.material:material:1.12.0' - implementation 'androidx.constraintlayout:constraintlayout:2.1.4' - implementation 'androidx.recyclerview:recyclerview:1.3.2' - implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' - implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0' - - implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.4' - implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.4' - implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.8.4' - - implementation 'androidx.navigation:navigation-fragment-ktx:2.8.0' - implementation 'androidx.navigation:navigation-ui-ktx:2.8.0' - - implementation 'com.squareup.retrofit2:retrofit:2.11.0' - implementation 'com.squareup.retrofit2:converter-gson:2.11.0' - implementation 'com.squareup.okhttp3:logging-interceptor:4.12.0' - - implementation 'io.coil-kt:coil:2.6.0' - - implementation 'androidx.room:room-runtime:2.6.1' - implementation 'androidx.room:room-ktx:2.6.1' - ksp 'androidx.room:room-compiler:2.6.1' - - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1' - - implementation 'androidx.datastore:datastore-preferences:1.1.1' -} \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml deleted file mode 100644 index 1d162ce..0000000 --- a/android/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/localstore/LocalStoreApp.kt b/android/app/src/main/kotlin/com/localstore/LocalStoreApp.kt deleted file mode 100644 index 13c30aa..0000000 --- a/android/app/src/main/kotlin/com/localstore/LocalStoreApp.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.localstore - -import android.app.Application -import com.localstore.data.AppDatabase - -class LocalStoreApp : Application() { - - val database: AppDatabase by lazy { - AppDatabase.getInstance(this) - } - - override fun onCreate() { - super.onCreate() - } -} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/localstore/data/AppApi.kt b/android/app/src/main/kotlin/com/localstore/data/AppApi.kt deleted file mode 100644 index 78bc7dc..0000000 --- a/android/app/src/main/kotlin/com/localstore/data/AppApi.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.localstore.data - -import okhttp3.ResponseBody -import retrofit2.http.Body -import retrofit2.http.GET -import retrofit2.http.POST -import retrofit2.http.Path -import retrofit2.http.Query - -interface AppApi { - @GET("api/apps") - suspend fun getApps( - @Query("search") search: String? = null, - @Query("offset") offset: Int = 0, - @Query("limit") limit: Int = 50 - ): AppListResponse - - @GET("api/apps/{appId}") - suspend fun getApp(@Path("appId") appId: String): App - - @GET("api/apps/{appId}/download") - suspend fun downloadApp(@Path("appId") appId: String): ResponseBody - - @GET("api/apps/{appId}/icon") - suspend fun getAppIcon(@Path("appId") appId: String): ResponseBody - - @POST("api/apps/update-check") - suspend fun checkForUpdate(@Body request: UpdateCheckRequest): UpdateCheckResponse - - @POST("api/scan") - suspend fun triggerScan(): ScanResult - - @GET("api/status") - suspend fun getServerStatus(): ServerStatus -} - -data class ScanResult( - val scanned: Boolean, - val appsCount: Int -) \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/localstore/data/AppDatabase.kt b/android/app/src/main/kotlin/com/localstore/data/AppDatabase.kt deleted file mode 100644 index cfdb210..0000000 --- a/android/app/src/main/kotlin/com/localstore/data/AppDatabase.kt +++ /dev/null @@ -1,186 +0,0 @@ -package com.localstore.data - -import android.content.Context -import androidx.room.Dao -import androidx.room.Database -import androidx.room.Delete -import androidx.room.Entity -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.PrimaryKey -import androidx.room.Query -import androidx.room.Room -import androidx.room.RoomDatabase -import androidx.room.TypeConverter -import androidx.room.TypeConverters -import androidx.room.Update -import com.google.gson.Gson -import com.google.gson.reflect.TypeToken -import kotlinx.coroutines.flow.Flow - -@Entity(tableName = "apps") -data class AppEntity( - @PrimaryKey val id: String, - val packageName: String, - val versionName: String, - val versionCode: String, - val name: String, - val description: String, - val icon: String?, - val size: Long, - val screenshotsJson: String = "[]", - val minSdk: String?, - val targetSdk: String?, - val permissionsJson: String = "[]" -) - -data class App( - val id: String, - val packageName: String, - val versionName: String, - val versionCode: String, - val name: String, - val description: String, - val icon: String?, - val size: Long, - val screenshots: List = emptyList(), - val minSdk: String?, - val targetSdk: String?, - val permissions: List = emptyList() -) - -fun App.toEntity(): AppEntity { - return AppEntity( - id = id, - packageName = packageName, - versionName = versionName, - versionCode = versionCode, - name = name, - description = description, - icon = icon, - size = size, - screenshotsJson = Gson().toJson(screenshots), - minSdk = minSdk, - targetSdk = targetSdk, - permissionsJson = Gson().toJson(permissions) - ) -} - -fun AppEntity.toApp(): App { - return App( - id = id, - packageName = packageName, - versionName = versionName, - versionCode = versionCode, - name = name, - description = description, - icon = icon, - size = size, - screenshots = try { - val type = object : TypeToken>() {}.type - Gson().fromJson>(screenshotsJson, type) ?: emptyList() - } catch (e: Exception) { - emptyList() - }, - minSdk = minSdk, - targetSdk = targetSdk, - permissions = try { - val type = object : TypeToken>() {}.type - Gson().fromJson>(permissionsJson, type) ?: emptyList() - } catch (e: Exception) { - emptyList() - } - ) -} - -data class AppListResponse( - val apps: List, - val total: Int -) - -data class UpdateCheckRequest( - val packageName: String, - val currentVersionCode: String -) - -data class UpdateCheckResponse( - val hasUpdate: Boolean, - val app: App?, - val currentVersionCode: String, - val latestVersionCode: String? -) - -data class ServerStatus( - val server: String, - val version: String, - val appsCount: Int -) - -class AppTypeConverter { - private val gson = Gson() - - @TypeConverter - fun fromStringList(value: String?): List { - if (value.isNullOrEmpty()) return emptyList() - return try { - val type = object : TypeToken>() {}.type - gson.fromJson>(value, type) - } catch (e: Exception) { - emptyList() - } - } - - @TypeConverter - fun toStringList(list: List): String { - return gson.toJson(list) - } -} - -@Dao -@TypeConverters(AppTypeConverter::class) -interface AppDao { - @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insertApps(apps: List) - - @Query("SELECT * FROM apps ORDER BY name ASC") - fun getAllApps(): Flow> - - @Query("SELECT * FROM apps WHERE id = :id") - suspend fun getAppById(id: String): AppEntity? - - @Query("SELECT * FROM apps WHERE packageName = :packageName") - suspend fun getAppByPackage(packageName: String): AppEntity? - - @Query("SELECT * FROM apps WHERE name LIKE :query OR description LIKE :query ORDER BY name ASC") - fun searchApps(query: String): Flow> - - @Delete - suspend fun deleteApp(app: AppEntity) - - @Query("DELETE FROM apps") - suspend fun clearAll() - - @Update - suspend fun updateApp(app: AppEntity) -} - -@Database(entities = [AppEntity::class], version = 1, exportSchema = false) -@TypeConverters(AppTypeConverter::class) -abstract class AppDatabase : RoomDatabase() { - abstract fun appDao(): AppDao - - companion object { - @Volatile - private var INSTANCE: AppDatabase? = null - - fun getInstance(context: Context): AppDatabase { - return INSTANCE ?: synchronized(this) { - INSTANCE ?: Room.databaseBuilder( - context.applicationContext, - AppDatabase::class.java, - "app_store_database" - ).build().also { INSTANCE = it } - } - } - } -} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/localstore/data/AppRepository.kt b/android/app/src/main/kotlin/com/localstore/data/AppRepository.kt deleted file mode 100644 index 55633ac..0000000 --- a/android/app/src/main/kotlin/com/localstore/data/AppRepository.kt +++ /dev/null @@ -1,86 +0,0 @@ -package com.localstore.data - -import android.content.Context -import com.localstore.LocalStoreApp -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import okhttp3.OkHttpClient -import okhttp3.ResponseBody -import okhttp3.logging.HttpLoggingInterceptor -import retrofit2.Retrofit -import retrofit2.converter.gson.GsonConverterFactory -import java.util.concurrent.TimeUnit - -class AppRepository( - private val context: Context, - defaultServerUrl: String = "http://192.168.8.128:9800" -) { - private val appDao = (context.applicationContext as LocalStoreApp).database.appDao() - @Volatile private var serverUrl = defaultServerUrl - @Volatile private var api: AppApi = createApi(defaultServerUrl) - - private fun createApi(url: String): AppApi { - val logging = HttpLoggingInterceptor().apply { - level = HttpLoggingInterceptor.Level.BODY - } - val client = OkHttpClient.Builder() - .addInterceptor(logging) - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) - .build() - - return Retrofit.Builder() - .baseUrl("$url/") - .client(client) - .addConverterFactory(GsonConverterFactory.create()) - .build() - .create(AppApi::class.java) - } - - fun setServerUrl(url: String) { - serverUrl = url - api = createApi(url) - } - - fun getAppsFlow(): Flow> = appDao.getAllApps().map { entities -> - entities.map { it.toApp() } - } - - fun searchAppsFlow(query: String): Flow> = - appDao.searchApps("%$query%").map { entities -> - entities.map { it.toApp() } - } - - suspend fun refreshApps() { - val response = api.getApps() - appDao.clearAll() - appDao.insertApps(response.apps.map { it.toEntity() }) - } - - suspend fun getAppById(id: String): App? { - val cached = appDao.getAppById(id)?.toApp() - if (cached != null) return cached - return runCatching { - val response = api.getApp(id) - val entity = response.toEntity() - appDao.insertApps(listOf(entity)) - response - }.getOrNull() - } - - suspend fun getAppByPackage(packageName: String): App? { - return appDao.getAppByPackage(packageName)?.toApp() - } - - suspend fun checkForUpdate(packageName: String, currentVersionCode: String): UpdateCheckResponse { - return api.checkForUpdate(UpdateCheckRequest(packageName, currentVersionCode)) - } - - suspend fun downloadApp(id: String): ResponseBody { - return api.downloadApp(id) - } - - suspend fun getServerStatus(): ServerStatus { - return api.getServerStatus() - } -} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/localstore/data/SettingsManager.kt b/android/app/src/main/kotlin/com/localstore/data/SettingsManager.kt deleted file mode 100644 index ccfd71c..0000000 --- a/android/app/src/main/kotlin/com/localstore/data/SettingsManager.kt +++ /dev/null @@ -1,80 +0,0 @@ -package com.localstore.data - -import android.content.Context -import androidx.datastore.preferences.core.Preferences -import androidx.datastore.preferences.core.stringPreferencesKey -import androidx.datastore.preferences.preferencesDataStore -import androidx.datastore.preferences.core.edit -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.map -import retrofit2.Retrofit - -object PreferencesKeys { - val SERVER_ADDRESS = stringPreferencesKey("server_address") - val SERVER_PORT = stringPreferencesKey("server_port") -} - -private val Context.dataStore by preferencesDataStore("settings") - -class SettingsManager(private val context: Context) { - - fun getServerAddressFlow(): Flow = - context.dataStore.data - .catch { throw it } - .map { prefs -> - prefs[PreferencesKeys.SERVER_ADDRESS] ?: "192.168.8.128" - } - - fun getServerPortFlow(): Flow = - context.dataStore.data - .catch { throw it } - .map { prefs -> - prefs[PreferencesKeys.SERVER_PORT] ?: "8080" - } - - fun getServerUrlFlow(): Flow = - context.dataStore.data - .catch { throw it } - .map { prefs -> - val address = prefs[PreferencesKeys.SERVER_ADDRESS] ?: "192.168.8.128" - val port = prefs[PreferencesKeys.SERVER_PORT] ?: "9800" - "http://$address:$port" - } - - suspend fun setServerAddress(address: String) { - context.dataStore.edit { prefs -> - prefs[PreferencesKeys.SERVER_ADDRESS] = address - } - } - - suspend fun setServerPort(port: String) { - context.dataStore.edit { prefs -> - prefs[PreferencesKeys.SERVER_PORT] = port - } - } - - suspend fun testConnection(): Result { - val address = context.dataStore.data.catch { throw it } - .map { prefs -> prefs[PreferencesKeys.SERVER_ADDRESS] ?: "192.168.8.128" } - .first() - val port = context.dataStore.data.catch { throw it } - .map { prefs -> prefs[PreferencesKeys.SERVER_PORT] ?: "9800" } - .first() - - return runCatching { - val client = okhttp3.OkHttpClient.Builder() - .connectTimeout(5, java.util.concurrent.TimeUnit.SECONDS) - .readTimeout(10, java.util.concurrent.TimeUnit.SECONDS) - .build() - val retrofit = Retrofit.Builder() - .baseUrl("http://$address:$port/") - .client(client) - .addConverterFactory(retrofit2.converter.gson.GsonConverterFactory.create()) - .build() - val api = retrofit.create(AppApi::class.java) - api.getServerStatus() - } - } -} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/localstore/installer/AppInstaller.kt b/android/app/src/main/kotlin/com/localstore/installer/AppInstaller.kt deleted file mode 100644 index ab15894..0000000 --- a/android/app/src/main/kotlin/com/localstore/installer/AppInstaller.kt +++ /dev/null @@ -1,122 +0,0 @@ -package com.localstore.installer - -import android.content.Context -import android.content.Intent -import android.content.pm.PackageInstaller -import android.content.pm.PackageManager -import android.net.Uri -import android.os.Build -import android.provider.Settings -import android.util.Log -import java.io.File -import java.io.InputStream - -class AppInstaller(private val context: Context) { - - private val packageManager: PackageManager = context.packageManager - - fun isAppInstalled(packageName: String): Boolean { - return try { - packageManager.getPackageInfo(packageName, 0) - true - } catch (e: Exception) { - false - } - } - - fun getInstalledPackages(): Set { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - packageManager.getInstalledPackages(PackageManager.MATCH_ALL) - .map { it.packageName } - .toSet() - } else { - @Suppress("DEPRECATION") - packageManager.getInstalledPackages(0) - .map { it.packageName } - .toSet() - } - } - - fun getPackageInfo(packageName: String): android.content.pm.PackageInfo? { - return try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - packageManager.getPackageInfo(packageName, PackageManager.MATCH_ALL) - } else { - @Suppress("DEPRECATION") - packageManager.getPackageInfo(packageName, 0) - } - } catch (e: Exception) { - null - } - } - - fun canInstall(): Boolean { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - packageManager.canRequestPackageInstalls() - } else { - true - } - } - - fun openInstallPermissionSettings() { - val intent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES) - intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK - intent.data = Uri.parse("package:${context.packageName}") - context.startActivity(intent) - } - - fun prepareInstall(inputStream: InputStream): File { - val tempFile = File(context.cacheDir, "download.apk") - tempFile.outputStream().use { outputStream -> - inputStream.copyTo(outputStream) - } - return tempFile - } - - fun install(apkFile: File): Boolean { - if (!canInstall()) { - Log.e("AppInstaller", "Install permission not granted") - return false - } - - val packageInstaller = packageManager.packageInstaller - val sessionParams = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL) - val sessionId = packageInstaller.createSession(sessionParams) - - packageInstaller.openSession(sessionId).use { session -> - val fis = java.io.FileInputStream(apkFile) - val buffer = ByteArray(65536) - var bytes: Int - session.openWrite("base", 0, -1).use { outputStream -> - while (fis.read(buffer).also { bytes = it } > 0) { - outputStream.write(buffer, 0, bytes) - session.fsync(outputStream) - } - } - fis.close() - - val intent = Intent(context, com.localstore.ui.MainActivity::class.java) - intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP - val pendingIntent = android.app.PendingIntent.getActivity( - context, 0, intent, - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) - android.app.PendingIntent.FLAG_IMMUTABLE or android.app.PendingIntent.FLAG_UPDATE_CURRENT - else - android.app.PendingIntent.FLAG_UPDATE_CURRENT - ) - session.commit(pendingIntent.intentSender) - } - - apkFile.delete() - return true - } - - @Suppress("DEPRECATION") - fun installViaIntent(apkFile: File): Intent { - val intent = Intent(Intent.ACTION_INSTALL_PACKAGE) - intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK - intent.data = Uri.fromFile(apkFile) - intent.putExtra(Intent.EXTRA_RETURN_RESULT, true) - return intent - } -} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/localstore/ui/MainActivity.kt b/android/app/src/main/kotlin/com/localstore/ui/MainActivity.kt deleted file mode 100644 index d6c631a..0000000 --- a/android/app/src/main/kotlin/com/localstore/ui/MainActivity.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.localstore.ui - -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity -import androidx.navigation.findNavController -import androidx.navigation.fragment.NavHostFragment -import androidx.navigation.ui.setupWithNavController -import com.localstore.R -import com.localstore.databinding.ActivityMainBinding - -class MainActivity : AppCompatActivity() { - - lateinit var binding: ActivityMainBinding - lateinit var navController: androidx.navigation.NavController - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - binding = ActivityMainBinding.inflate(layoutInflater) - setContentView(binding.root) - - val navHostFragment = NavHostFragment.create(R.navigation.nav_graph) - supportFragmentManager.beginTransaction() - .replace(R.id.nav_host_container, navHostFragment) - .commitAllowingStateLoss() - - navController = navHostFragment.navController - binding.bottomNav.setupWithNavController(navController) - } -} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/localstore/ui/details/DetailsFragment.kt b/android/app/src/main/kotlin/com/localstore/ui/details/DetailsFragment.kt deleted file mode 100644 index fa294ce..0000000 --- a/android/app/src/main/kotlin/com/localstore/ui/details/DetailsFragment.kt +++ /dev/null @@ -1,168 +0,0 @@ -package com.localstore.ui.details - -import android.content.Context -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.Toast -import androidx.fragment.app.Fragment -import androidx.fragment.app.viewModels -import androidx.lifecycle.lifecycleScope -import coil.load -import com.localstore.R -import com.localstore.data.App -import com.localstore.databinding.FragmentDetailsBinding -import com.localstore.installer.AppInstaller -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.launch -import java.text.DecimalFormat - -class DetailsFragment : Fragment() { - - private var _binding: FragmentDetailsBinding? = null - private val binding get() = _binding!! - - private val viewModel: DetailsViewModel by viewModels { - androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory(requireActivity().application) - } - - private val installer = AppInstaller(requireContext()) - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - _binding = FragmentDetailsBinding.inflate(inflater, container, false) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - val appId = arguments?.getString(ARG_APP_ID) ?: run { - requireActivity().onBackPressedDispatcher.onBackPressed() - return - } - - viewModel.loadApp(appId) - - viewLifecycleOwner.lifecycleScope.launch { - viewModel.app.collect { app -> - app?.let { bindApp(it) } - } - } - - viewLifecycleOwner.lifecycleScope.launch { - viewModel.installState.collect { state -> - binding.installButton.apply { - when (state) { - is InstallState.Idle -> { - text = if (viewModel.isInstalled.value) requireContext().getString(R.string.installed) else requireContext().getString(R.string.install) - isEnabled = true - binding.progressBar.visibility = View.GONE - } - is InstallState.Downloading -> { - text = requireContext().getString(R.string.downloading) - isEnabled = false - binding.progressBar.visibility = View.VISIBLE - } - is InstallState.Installing -> { - text = requireContext().getString(R.string.installing) - isEnabled = false - binding.progressBar.visibility = View.VISIBLE - } - is InstallState.Success -> { - text = requireContext().getString(R.string.installed) - isEnabled = false - binding.progressBar.visibility = View.GONE - Toast.makeText(requireContext(), requireContext().getString(R.string.install_success), Toast.LENGTH_SHORT).show() - } - is InstallState.Failed -> { - text = requireContext().getString(R.string.install) - isEnabled = true - binding.progressBar.visibility = View.GONE - Toast.makeText(requireContext(), state.message, Toast.LENGTH_LONG).show() - } - } - } - } - } - - viewLifecycleOwner.lifecycleScope.launch { - viewModel.isInstalled.collect { installed -> - if (!installed && viewModel.installState.value is InstallState.Idle) { - binding.installButton.text = requireContext().getString(R.string.install) - } - } - } - - binding.installButton.setOnClickListener { - if (!installer.canInstall()) { - Toast.makeText(requireContext(), R.string.grant_install_permission, Toast.LENGTH_LONG).show() - installer.openInstallPermissionSettings() - return@setOnClickListener - } - viewModel.installApp() - } - - binding.backButton.setOnClickListener { - requireActivity().onBackPressedDispatcher.onBackPressed() - } - } - - private fun bindApp(app: App) { - val serverHost = viewModel.serverUrl.value - val host = serverHost.replace("http://", "").split(":").first() - - binding.apply { - appName.text = app.name - appVersion.text = "Version: ${app.versionName}" - appPackage.text = app.packageName - - val sizeStr = when { - app.size > 1_000_000 -> String.format("%.1f MB", app.size / 1_000_000.0) - app.size > 1_000 -> String.format("%.1f KB", app.size / 1_000.0) - else -> "${app.size} B" - } - appSize.text = "Size: $sizeStr" - - appDescription.text = if (app.description.isNotBlank()) app.description else "No description available" - - if (!app.icon.isNullOrBlank()) { - appIcon.load("http://$host:9800/api/apps/${app.id}/icon") { - crossfade(true) - placeholder(android.R.drawable.ic_dialog_info) - } - } else { - appIcon.setImageResource(android.R.drawable.ic_dialog_info) - } - - if (app.permissions.isNotEmpty()) { - appPermissionsContainer.visibility = View.VISIBLE - val permissionsText = app.permissions.joinToString("\n") { "• $it" } - appPermissionsList.text = permissionsText - } else { - appPermissionsContainer.visibility = View.GONE - } - } - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } - - companion object { - private const val ARG_APP_ID = "app_id" - - fun newInstance(appId: String): DetailsFragment { - return DetailsFragment().apply { - arguments = Bundle().apply { - putString(ARG_APP_ID, appId) - } - } - } - } -} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/localstore/ui/details/DetailsViewModel.kt b/android/app/src/main/kotlin/com/localstore/ui/details/DetailsViewModel.kt deleted file mode 100644 index 43fef5f..0000000 --- a/android/app/src/main/kotlin/com/localstore/ui/details/DetailsViewModel.kt +++ /dev/null @@ -1,86 +0,0 @@ -package com.localstore.ui.details - -import android.content.Context -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.localstore.data.App -import com.localstore.data.AppRepository -import com.localstore.data.SettingsManager -import com.localstore.installer.AppInstaller -import kotlinx.coroutines.flow.* -import kotlinx.coroutines.launch - -sealed class InstallState { - object Idle : InstallState() - object Downloading : InstallState() - object Installing : InstallState() - data class Success(val app: App) : InstallState() - data class Failed(val message: String) : InstallState() -} - -class DetailsViewModel( - private val context: Context -) : ViewModel() { - - private val settingsManager = SettingsManager(context) - private val repository = AppRepository(context) - private val installer = AppInstaller(context) - - val serverUrl = MutableStateFlow("http://192.168.8.128:9800") - - init { - viewModelScope.launch { - settingsManager.getServerUrlFlow().collect { url -> - serverUrl.value = url - repository.setServerUrl(url) - } - } - } - - val app = MutableStateFlow(null) - val installState = MutableStateFlow(InstallState.Idle) - val downloadProgress = MutableStateFlow(0) - val isInstalled = MutableStateFlow(false) - - fun loadApp(appId: String) { - viewModelScope.launch { - try { - val loadedApp = repository.getAppById(appId) - app.value = loadedApp - isInstalled.value = installer.isAppInstalled(loadedApp?.packageName ?: "") - } catch (e: Exception) { - e.printStackTrace() - } - } - } - - fun installApp() { - val currentApp = app.value ?: return - installState.value = InstallState.Downloading - downloadProgress.value = 0 - - viewModelScope.launch { - try { - val responseBody = repository.downloadApp(currentApp.id) - val tempFile = installer.prepareInstall(responseBody.byteStream()) - - installState.value = InstallState.Installing - val result = installer.install(tempFile) - - if (result) { - installState.value = InstallState.Success(currentApp) - isInstalled.value = true - } else { - installState.value = InstallState.Failed("Installation failed") - } - } catch (e: Exception) { - installState.value = InstallState.Failed(e.message ?: "Unknown error") - } - } - } - - fun resetInstallState() { - installState.value = InstallState.Idle - downloadProgress.value = 0 - } -} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/localstore/ui/home/AppsAdapter.kt b/android/app/src/main/kotlin/com/localstore/ui/home/AppsAdapter.kt deleted file mode 100644 index 62afec0..0000000 --- a/android/app/src/main/kotlin/com/localstore/ui/home/AppsAdapter.kt +++ /dev/null @@ -1,71 +0,0 @@ -package com.localstore.ui.home - -import android.content.Context -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.ListAdapter -import androidx.recyclerview.widget.RecyclerView -import coil.load -import com.localstore.data.App -import com.localstore.databinding.ItemAppBinding - -class AppsAdapter( - private val onItemClick: (App) -> Unit -) : ListAdapter(AppDiffCallback()) { - - var serverHost: String = "192.168.8.128" - var serverPort: Int = 9800 - var contextRef: Context? = null - - fun setServerInfo(host: String, port: Int, ctx: Context) { - serverHost = host - serverPort = port - contextRef = ctx - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AppViewHolder { - val binding = ItemAppBinding.inflate( - LayoutInflater.from(parent.context), - parent, - false - ) - return AppViewHolder(binding, serverHost, serverPort) - } - - override fun onBindViewHolder(holder: AppViewHolder, position: Int) { - holder.bind(getItem(position), onItemClick) - } - - class AppViewHolder( - private val binding: ItemAppBinding, - private val serverHost: String, - private val serverPort: Int - ) : RecyclerView.ViewHolder(binding.root) { - - fun bind(app: App, onItemClick: (App) -> Unit) { - binding.apply { - appName.text = app.name - appVersion.text = app.versionName - - appIcon.apply { - if (!app.icon.isNullOrBlank()) { - load("http://$serverHost:$serverPort/api/apps/${app.id}/icon") { - crossfade(true) - placeholder(android.R.drawable.ic_dialog_info) - } - } else { - setImageResource(android.R.drawable.ic_dialog_info) - } - } - - root.setOnClickListener { onItemClick(app) } - } - } - } - - class AppDiffCallback : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: App, newItem: App) = oldItem.id == newItem.id - override fun areContentsTheSame(oldItem: App, newItem: App) = oldItem == newItem - } -} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/localstore/ui/home/HomeFragment.kt b/android/app/src/main/kotlin/com/localstore/ui/home/HomeFragment.kt deleted file mode 100644 index ed8c050..0000000 --- a/android/app/src/main/kotlin/com/localstore/ui/home/HomeFragment.kt +++ /dev/null @@ -1,107 +0,0 @@ -package com.localstore.ui.home - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.Toast -import androidx.fragment.app.Fragment -import androidx.fragment.app.viewModels -import androidx.lifecycle.lifecycleScope -import androidx.recyclerview.widget.GridLayoutManager -import com.localstore.data.App -import com.localstore.databinding.FragmentHomeBinding -import com.localstore.ui.MainActivity -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.launch - -class HomeFragment : Fragment() { - - private var _binding: FragmentHomeBinding? = null - private val binding get() = _binding!! - - private val viewModel: HomeViewModel by viewModels { - androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory(requireActivity().application) - } - - private lateinit var adapter: AppsAdapter - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - _binding = FragmentHomeBinding.inflate(inflater, container, false) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - adapter = AppsAdapter { app -> - (requireActivity() as MainActivity).navController - .navigate(com.localstore.R.id.nav_details, Bundle().apply { putString("app_id", app.id) }) - } - - binding.recyclerView.apply { - layoutManager = GridLayoutManager(requireContext(), 3) - adapter = this@HomeFragment.adapter - } - - binding.searchEditText.addTextChangedListener(object : android.text.TextWatcher { - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { - viewModel.setSearchQuery(s.toString()) - } - override fun afterTextChanged(s: android.text.Editable?) {} - }) - - binding.swipeRefresh.setOnRefreshListener { - viewModel.refresh() - } - - viewLifecycleOwner.lifecycleScope.launch { - viewModel.apps.collect { apps -> - adapter.submitList(apps) - binding.swipeRefresh.isRefreshing = false - updateEmptyState(apps) - } - } - - viewLifecycleOwner.lifecycleScope.launch { - viewModel.searchResults.collect { apps -> - if (binding.searchEditText.text.isNotEmpty()) { - adapter.submitList(apps) - updateEmptyState(apps) - } - } - } - - viewLifecycleOwner.lifecycleScope.launch { - viewModel.isLoading.collect { isLoading -> - binding.swipeRefresh.isRefreshing = isLoading - } - } - - viewLifecycleOwner.lifecycleScope.launch { - viewModel.error.collect { error -> - error?.let { - Toast.makeText(requireContext(), it, Toast.LENGTH_LONG).show() - binding.swipeRefresh.isRefreshing = false - } - } - } - - viewModel.refresh() - } - - private fun updateEmptyState(apps: List) { - binding.emptyState.visibility = if (apps.isEmpty()) View.VISIBLE else View.GONE - binding.recyclerView.visibility = if (apps.isEmpty()) View.GONE else View.VISIBLE - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } -} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/localstore/ui/home/HomeViewModel.kt b/android/app/src/main/kotlin/com/localstore/ui/home/HomeViewModel.kt deleted file mode 100644 index 285cc9e..0000000 --- a/android/app/src/main/kotlin/com/localstore/ui/home/HomeViewModel.kt +++ /dev/null @@ -1,61 +0,0 @@ -package com.localstore.ui.home - -import android.content.Context -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.localstore.data.App -import com.localstore.data.AppRepository -import com.localstore.data.SettingsManager -import kotlinx.coroutines.flow.* -import kotlinx.coroutines.launch - -class HomeViewModel( - private val context: Context -) : ViewModel() { - - private val settingsManager = SettingsManager(context) - private val repository = AppRepository(context) - - init { - viewModelScope.launch { - settingsManager.getServerUrlFlow().collect { url -> - repository.setServerUrl(url) - } - } - } - - val apps = repository.getAppsFlow() - .stateIn(viewModelScope, SharingStarted.Lazily, emptyList()) - - val searchQuery = MutableStateFlow("") - val searchResults = searchQuery - .combine(repository.getAppsFlow()) { query, allApps -> - if (query.isBlank()) allApps - else allApps.filter { - it.name.lowercase().contains(query.lowercase()) || - it.description.lowercase().contains(query.lowercase()) - } - } - .stateIn(viewModelScope, SharingStarted.Lazily, emptyList()) - - val isLoading = MutableStateFlow(false) - val error = MutableStateFlow(null) - - fun refresh() { - isLoading.value = true - error.value = null - viewModelScope.launch { - try { - repository.refreshApps() - } catch (e: Exception) { - error.value = e.message ?: "Refresh failed" - } finally { - isLoading.value = false - } - } - } - - fun setSearchQuery(query: String) { - searchQuery.value = query - } -} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/localstore/ui/myapps/InstalledAppsAdapter.kt b/android/app/src/main/kotlin/com/localstore/ui/myapps/InstalledAppsAdapter.kt deleted file mode 100644 index 69bb730..0000000 --- a/android/app/src/main/kotlin/com/localstore/ui/myapps/InstalledAppsAdapter.kt +++ /dev/null @@ -1,60 +0,0 @@ -package com.localstore.ui.myapps - -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.ListAdapter -import androidx.recyclerview.widget.RecyclerView -import coil.load -import com.localstore.databinding.ItemInstalledAppBinding - -class InstalledAppsAdapter( - private val onItemClick: (InstalledApp) -> Unit -) : ListAdapter(InstalledAppDiffCallback()) { - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AppViewHolder { - val binding = ItemInstalledAppBinding.inflate( - LayoutInflater.from(parent.context), - parent, - false - ) - return AppViewHolder(binding) - } - - override fun onBindViewHolder(holder: AppViewHolder, position: Int) { - holder.bind(getItem(position), onItemClick) - } - - class AppViewHolder( - private val binding: ItemInstalledAppBinding - ) : RecyclerView.ViewHolder(binding.root) { - - fun bind(app: InstalledApp, onItemClick: (InstalledApp) -> Unit) { - binding.apply { - appName.text = app.app.name - appVersion.text = "v${app.installedVersionName}" - - if (!app.app.icon.isNullOrBlank()) { - appIcon.load("http://192.168.8.128:9800/api/apps/${app.app.id}/icon") { - crossfade(true) - placeholder(android.R.drawable.ic_dialog_info) - } - } else { - appIcon.setImageResource(android.R.drawable.ic_dialog_info) - } - - updateBadge.visibility = if (app.hasUpdate) View.VISIBLE else View.GONE - - root.setOnClickListener { onItemClick(app) } - } - } - } - - class InstalledAppDiffCallback : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: InstalledApp, newItem: InstalledApp) = - oldItem.app.id == newItem.app.id - override fun areContentsTheSame(oldItem: InstalledApp, newItem: InstalledApp) = - oldItem == newItem - } -} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/localstore/ui/myapps/MyAppsFragment.kt b/android/app/src/main/kotlin/com/localstore/ui/myapps/MyAppsFragment.kt deleted file mode 100644 index dbb9320..0000000 --- a/android/app/src/main/kotlin/com/localstore/ui/myapps/MyAppsFragment.kt +++ /dev/null @@ -1,92 +0,0 @@ -package com.localstore.ui.myapps - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import androidx.fragment.app.viewModels -import androidx.lifecycle.lifecycleScope -import androidx.recyclerview.widget.LinearLayoutManager -import com.localstore.databinding.FragmentMyAppsBinding -import com.localstore.ui.MainActivity -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.launch - -class MyAppsFragment : Fragment() { - - private var _binding: FragmentMyAppsBinding? = null - private val binding get() = _binding!! - - private val viewModel: MyAppsViewModel by viewModels { - androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory(requireActivity().application) - } - - private lateinit var adapter: InstalledAppsAdapter - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - _binding = FragmentMyAppsBinding.inflate(inflater, container, false) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - adapter = InstalledAppsAdapter { app -> - (requireActivity() as MainActivity).navController - .navigate(com.localstore.R.id.nav_details, Bundle().apply { putString("app_id", app.app.id) }) - } - - binding.recyclerView.apply { - layoutManager = LinearLayoutManager(requireContext()) - adapter = this@MyAppsFragment.adapter - } - - binding.checkUpdatesButton.setOnClickListener { - viewModel.checkForUpdates() - } - - viewLifecycleOwner.lifecycleScope.launch { - viewModel.installedApps.collect { apps -> - adapter.submitList(apps) - updateEmptyState(apps) - - val updateCount = apps.count { it.hasUpdate } - if (updateCount > 0) { - binding.updatesBadge.visibility = View.VISIBLE - binding.updatesBadge.text = updateCount.toString() - } else { - binding.updatesBadge.visibility = View.GONE - } - } - } - - viewLifecycleOwner.lifecycleScope.launch { - viewModel.isLoading.collect { isLoading -> - binding.progressBar.visibility = if (isLoading) View.VISIBLE else View.GONE - binding.checkUpdatesButton.isEnabled = !isLoading - } - } - - viewModel.loadInstalledApps() - } - - private fun updateEmptyState(apps: List) { - binding.emptyState.visibility = if (apps.isEmpty()) View.VISIBLE else View.GONE - binding.recyclerView.visibility = if (apps.isEmpty()) View.GONE else View.VISIBLE - } - - override fun onResume() { - super.onResume() - viewModel.loadInstalledApps() - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } -} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/localstore/ui/myapps/MyAppsViewModel.kt b/android/app/src/main/kotlin/com/localstore/ui/myapps/MyAppsViewModel.kt deleted file mode 100644 index 8f18aee..0000000 --- a/android/app/src/main/kotlin/com/localstore/ui/myapps/MyAppsViewModel.kt +++ /dev/null @@ -1,89 +0,0 @@ -package com.localstore.ui.myapps - -import android.content.Context -import android.content.pm.PackageInfo -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.localstore.data.App -import com.localstore.data.AppRepository -import com.localstore.data.SettingsManager -import com.localstore.installer.AppInstaller -import kotlinx.coroutines.flow.* -import kotlinx.coroutines.launch - -data class InstalledApp( - val app: App, - val installedVersionCode: Int, - val installedVersionName: String, - val hasUpdate: Boolean = false -) - -class MyAppsViewModel( - private val context: Context -) : ViewModel() { - - private val settingsManager = SettingsManager(context) - private val repository = AppRepository(context) - private val installer = AppInstaller(context) - - init { - viewModelScope.launch { - settingsManager.getServerUrlFlow().collect { url -> - repository.setServerUrl(url) - } - } - } - - val installedApps = MutableStateFlow>(emptyList()) - val isLoading = MutableStateFlow(false) - - fun loadInstalledApps() { - isLoading.value = true - viewModelScope.launch { - try { - val repoApps = repository.getAppsFlow().first() - val installed = installer.getInstalledPackages() - - val installedAppsList = repoApps.filter { repoApp -> - installed.contains(repoApp.packageName) - }.map { repoApp -> - val pkgInfo = installer.getPackageInfo(repoApp.packageName) - InstalledApp( - app = repoApp, - installedVersionCode = pkgInfo?.longVersionCode?.toInt() ?: 0, - installedVersionName = pkgInfo?.versionName ?: "unknown", - hasUpdate = false - ) - } - - installedApps.value = installedAppsList - } catch (e: Exception) { - e.printStackTrace() - } finally { - isLoading.value = false - } - } - } - - fun checkForUpdates() { - isLoading.value = true - viewModelScope.launch { - val currentApps = installedApps.value.toMutableList() - - for ((index, installedApp) in currentApps.withIndex()) { - try { - val response = repository.checkForUpdate( - installedApp.app.packageName, - installedApp.installedVersionCode.toString() - ) - currentApps[index] = installedApp.copy(hasUpdate = response.hasUpdate) - installedApps.value = currentApps - } catch (e: Exception) { - e.printStackTrace() - } - } - - isLoading.value = false - } - } -} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/localstore/ui/settings/SettingsFragment.kt b/android/app/src/main/kotlin/com/localstore/ui/settings/SettingsFragment.kt deleted file mode 100644 index 1389d88..0000000 --- a/android/app/src/main/kotlin/com/localstore/ui/settings/SettingsFragment.kt +++ /dev/null @@ -1,124 +0,0 @@ -package com.localstore.ui.settings - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.Toast -import androidx.fragment.app.Fragment -import androidx.fragment.app.viewModels -import androidx.lifecycle.lifecycleScope -import com.localstore.R -import com.localstore.databinding.FragmentSettingsBinding -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.launch - -class SettingsFragment : Fragment() { - - private var _binding: FragmentSettingsBinding? = null - private val binding get() = _binding!! - - private val viewModel: SettingsViewModel by viewModels { - androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory(requireActivity().application) - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - _binding = FragmentSettingsBinding.inflate(inflater, container, false) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - viewLifecycleOwner.lifecycleScope.launch { - viewModel.serverAddress.collect { address -> - binding.serverAddressEditText.setText(address) - } - } - - viewLifecycleOwner.lifecycleScope.launch { - viewModel.serverPort.collect { port -> - binding.serverPortEditText.setText(port) - } - } - - viewLifecycleOwner.lifecycleScope.launch { - viewModel.testResult.collect { result -> - when (result) { - is SettingsViewModel.TestResult.Idle -> { - binding.testButton.isEnabled = true - binding.testResultText.visibility = View.GONE - } - is SettingsViewModel.TestResult.Testing -> { - binding.testButton.isEnabled = false - binding.testResultText.visibility = View.VISIBLE - binding.testResultText.text = "${requireContext().getString(R.string.connection_test)}..." - binding.testResultText.setTextColor(requireContext().getColor(android.R.color.darker_gray)) - } - is SettingsViewModel.TestResult.Success -> { - binding.testButton.isEnabled = true - binding.testResultText.visibility = View.VISIBLE - binding.testResultText.text = String.format( - requireContext().getString(R.string.connection_success), - result.status.appsCount - ) - binding.testResultText.setTextColor(requireContext().getColor(R.color.primary)) - } - is SettingsViewModel.TestResult.Failed -> { - binding.testButton.isEnabled = true - binding.testResultText.visibility = View.VISIBLE - binding.testResultText.text = result.message - binding.testResultText.setTextColor(requireContext().getColor(R.color.error)) - } - null -> { - binding.testButton.isEnabled = true - binding.testResultText.visibility = View.GONE - } - } - } - } - - binding.saveButton.setOnClickListener { - val address = binding.serverAddressEditText.text.toString().trim() - val port = binding.serverPortEditText.text.toString().trim() - - if (address.isEmpty()) { - Toast.makeText(requireContext(), "Server address is required", Toast.LENGTH_SHORT).show() - return@setOnClickListener - } - - viewModel.setServerAddress(address) - if (port.isNotEmpty()) { - viewModel.setServerPort(port) - } - - Toast.makeText(requireContext(), "Settings saved", Toast.LENGTH_SHORT).show() - } - - binding.testButton.setOnClickListener { - val address = binding.serverAddressEditText.text.toString().trim() - val port = binding.serverPortEditText.text.toString().trim() - - if (address.isEmpty()) { - Toast.makeText(requireContext(), "Enter server address first", Toast.LENGTH_SHORT).show() - return@setOnClickListener - } - - viewModel.setServerAddress(address) - if (port.isNotEmpty()) { - viewModel.setServerPort(port) - } - - viewModel.testConnection() - } - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } -} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/localstore/ui/settings/SettingsViewModel.kt b/android/app/src/main/kotlin/com/localstore/ui/settings/SettingsViewModel.kt deleted file mode 100644 index be85a78..0000000 --- a/android/app/src/main/kotlin/com/localstore/ui/settings/SettingsViewModel.kt +++ /dev/null @@ -1,59 +0,0 @@ -package com.localstore.ui.settings - -import android.content.Context -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.localstore.data.AppApi -import com.localstore.data.ServerStatus -import com.localstore.data.SettingsManager -import kotlinx.coroutines.flow.* -import kotlinx.coroutines.launch - -class SettingsViewModel( - private val context: Context -) : ViewModel() { - - private val settingsManager = SettingsManager(context) - - val serverAddress = settingsManager.getServerAddressFlow() - .stateIn(viewModelScope, SharingStarted.Eagerly, "192.168.8.128") - - val serverPort = settingsManager.getServerPortFlow() - .stateIn(viewModelScope, SharingStarted.Eagerly, "9800") - - val serverUrl = settingsManager.getServerUrlFlow() - .stateIn(viewModelScope, SharingStarted.Lazily, "") - - val testResult = MutableStateFlow(null) - - sealed class TestResult { - object Idle : TestResult() - object Testing : TestResult() - data class Success(val status: ServerStatus) : TestResult() - data class Failed(val message: String) : TestResult() - } - - fun setServerAddress(address: String) { - viewModelScope.launch { - settingsManager.setServerAddress(address) - } - } - - fun setServerPort(port: String) { - viewModelScope.launch { - settingsManager.setServerPort(port) - } - } - - fun testConnection() { - testResult.value = TestResult.Testing - viewModelScope.launch { - val result = settingsManager.testConnection() - testResult.value = if (result.isSuccess) { - TestResult.Success(result.getOrNull()!!) - } else { - TestResult.Failed(result.exceptionOrNull()?.message ?: "Connection failed") - } - } - } -} \ No newline at end of file diff --git a/android/app/src/main/res/drawable/badge_background.xml b/android/app/src/main/res/drawable/badge_background.xml deleted file mode 100644 index 644d400..0000000 --- a/android/app/src/main/res/drawable/badge_background.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/drawable/ic_launcher_foreground.xml b/android/app/src/main/res/drawable/ic_launcher_foreground.xml deleted file mode 100644 index 887b615..0000000 --- a/android/app/src/main/res/drawable/ic_launcher_foreground.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index 7ec0822..0000000 --- a/android/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/layout/fragment_details.xml b/android/app/src/main/res/layout/fragment_details.xml deleted file mode 100644 index 64702c2..0000000 --- a/android/app/src/main/res/layout/fragment_details.xml +++ /dev/null @@ -1,160 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/layout/fragment_home.xml b/android/app/src/main/res/layout/fragment_home.xml deleted file mode 100644 index b9b7fb6..0000000 --- a/android/app/src/main/res/layout/fragment_home.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/layout/fragment_my_apps.xml b/android/app/src/main/res/layout/fragment_my_apps.xml deleted file mode 100644 index 4e34676..0000000 --- a/android/app/src/main/res/layout/fragment_my_apps.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/layout/fragment_settings.xml b/android/app/src/main/res/layout/fragment_settings.xml deleted file mode 100644 index a3cb36f..0000000 --- a/android/app/src/main/res/layout/fragment_settings.xml +++ /dev/null @@ -1,105 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/layout/item_app.xml b/android/app/src/main/res/layout/item_app.xml deleted file mode 100644 index 5780f5b..0000000 --- a/android/app/src/main/res/layout/item_app.xml +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/layout/item_installed_app.xml b/android/app/src/main/res/layout/item_installed_app.xml deleted file mode 100644 index 712e0ba..0000000 --- a/android/app/src/main/res/layout/item_installed_app.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/menu/bottom_nav_menu.xml b/android/app/src/main/res/menu/bottom_nav_menu.xml deleted file mode 100644 index 68caa4f..0000000 --- a/android/app/src/main/res/menu/bottom_nav_menu.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-anydpi/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi/ic_launcher.xml deleted file mode 100644 index c3b6456..0000000 --- a/android/app/src/main/res/mipmap-anydpi/ic_launcher.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml b/android/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml deleted file mode 100644 index c3b6456..0000000 --- a/android/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/navigation/nav_graph.xml b/android/app/src/main/res/navigation/nav_graph.xml deleted file mode 100644 index 3e67e22..0000000 --- a/android/app/src/main/res/navigation/nav_graph.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml deleted file mode 100644 index 754afc9..0000000 --- a/android/app/src/main/res/values/colors.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - #1B96FF - #006EC4 - #73BFFF - #FF4081 - #FAFAFA - #FFFFFF - #FFFFFF - #1C1C1C - #1C1C1C - #B00020 - \ No newline at end of file diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml deleted file mode 100644 index b311666..0000000 --- a/android/app/src/main/res/values/strings.xml +++ /dev/null @@ -1,53 +0,0 @@ - - Local App Store - 192.168.8.128 - - - Apps - My Apps - Settings - - - Search apps... - No apps found - Pull to refresh - - - Install - Update - Installed - Downloading... - Installing... - Description - Version - Size - Package - Permissions - - - No apps installed from this store - Check for updates - Updates available - All apps up to date - - - Server Settings - Server Address - e.g. 192.168.8.128 - Server Port - e.g. 9800 - Test Connection - Connected! %d apps available. - Connection failed. Check server address. - Save - - - App installed successfully - Installation failed: %s - Download failed: %s - - - Network error. Check your connection. - An unexpected error occurred. - Please grant "Install unknown apps" permission in Settings. - \ No newline at end of file diff --git a/android/app/src/main/res/values/themes.xml b/android/app/src/main/res/values/themes.xml deleted file mode 100644 index 7185456..0000000 --- a/android/app/src/main/res/values/themes.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - \ No newline at end of file diff --git a/android/app/src/main/res/xml/network_security_config.xml b/android/app/src/main/res/xml/network_security_config.xml deleted file mode 100644 index 3ea0618..0000000 --- a/android/app/src/main/res/xml/network_security_config.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - 192.168.8.128 - 192.168. - 10.0. - localhost - - \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle deleted file mode 100644 index b380c30..0000000 --- a/android/build.gradle +++ /dev/null @@ -1,9 +0,0 @@ -plugins { - id 'com.android.application' version '8.5.2' apply false - id 'org.jetbrains.kotlin.android' version '1.9.23' apply false - id 'com.google.devtools.ksp' version '1.9.23-1.0.20' apply false -} - -tasks.register("clean", Delete) { - delete rootProject.buildDir -} \ No newline at end of file diff --git a/android/gradle.properties b/android/gradle.properties deleted file mode 100644 index 0dee8cb..0000000 --- a/android/gradle.properties +++ /dev/null @@ -1,4 +0,0 @@ -org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 -android.useAndroidX=true -kotlin.code.style=official -android.nonTransitiveRClass=true \ No newline at end of file diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index e644113..0000000 Binary files a/android/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index b82aa23..0000000 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,7 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/android/gradlew b/android/gradlew deleted file mode 100755 index 1aa94a4..0000000 --- a/android/gradlew +++ /dev/null @@ -1,249 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# 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. -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, -# and any embedded shellness will be escaped. -# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be -# treated as '${Hostname}' itself on the command line. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/android/gradlew.bat b/android/gradlew.bat deleted file mode 100644 index 7101f8e..0000000 --- a/android/gradlew.bat +++ /dev/null @@ -1,92 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/android/settings.gradle b/android/settings.gradle deleted file mode 100644 index 3a515af..0000000 --- a/android/settings.gradle +++ /dev/null @@ -1,18 +0,0 @@ -pluginManagement { - repositories { - google() - mavenCentral() - gradlePluginPortal() - } -} - -dependencyResolutionManagement { - repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) - repositories { - google() - mavenCentral() - } -} - -rootProject.name = "LocalAppStore" -include ':app' \ No newline at end of file diff --git a/helloworld/app/build.gradle b/helloworld/app/build.gradle deleted file mode 100644 index 6c69520..0000000 --- a/helloworld/app/build.gradle +++ /dev/null @@ -1,53 +0,0 @@ -plugins { - id 'com.android.application' - id 'org.jetbrains.kotlin.android' -} - -repositories { - google() - mavenCentral() -} - -android { - namespace 'com.helloworld' - compileSdk 34 - - defaultConfig { - applicationId "com.helloworld" - minSdk 26 - targetSdk 34 - versionCode 1 - versionName "1.0" - } - - signingConfigs { - release { - storeFile file("${System.getenv('HOME')}/playground/app-store/signing/localstore.keystore") - storePassword "localstore123" - keyAlias "localstore" - keyPassword "localstore123" - } - } - - buildTypes { - release { - minifyEnabled false - signingConfig signingConfigs.release - } - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 - } - - kotlinOptions { - jvmTarget = "17" - } -} - -dependencies { - implementation 'androidx.core:core-ktx:1.13.1' - implementation 'androidx.appcompat:appcompat:1.7.0' - implementation 'com.google.android.material:material:1.12.0' -} \ No newline at end of file diff --git a/helloworld/app/src/main/AndroidManifest.xml b/helloworld/app/src/main/AndroidManifest.xml deleted file mode 100644 index 46fd45e..0000000 --- a/helloworld/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/helloworld/app/src/main/kotlin/com/helloworld/MainActivity.kt b/helloworld/app/src/main/kotlin/com/helloworld/MainActivity.kt deleted file mode 100644 index c617374..0000000 --- a/helloworld/app/src/main/kotlin/com/helloworld/MainActivity.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.helloworld - -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity - -class MainActivity : AppCompatActivity() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - } -} \ No newline at end of file diff --git a/helloworld/app/src/main/res/drawable/ic_launcher_foreground.xml b/helloworld/app/src/main/res/drawable/ic_launcher_foreground.xml deleted file mode 100644 index 788f432..0000000 --- a/helloworld/app/src/main/res/drawable/ic_launcher_foreground.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - \ No newline at end of file diff --git a/helloworld/app/src/main/res/layout/activity_main.xml b/helloworld/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index a61a783..0000000 --- a/helloworld/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/helloworld/app/src/main/res/mipmap-hdpi/ic_launcher.xml b/helloworld/app/src/main/res/mipmap-hdpi/ic_launcher.xml deleted file mode 100644 index 7353dbd..0000000 --- a/helloworld/app/src/main/res/mipmap-hdpi/ic_launcher.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/helloworld/app/src/main/res/values/colors.xml b/helloworld/app/src/main/res/values/colors.xml deleted file mode 100644 index a70c314..0000000 --- a/helloworld/app/src/main/res/values/colors.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - #1B96FF - \ No newline at end of file diff --git a/helloworld/app/src/main/res/values/strings.xml b/helloworld/app/src/main/res/values/strings.xml deleted file mode 100644 index b067902..0000000 --- a/helloworld/app/src/main/res/values/strings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - Hello World - Hello World! - \ No newline at end of file diff --git a/helloworld/build.gradle b/helloworld/build.gradle deleted file mode 100644 index 5ef991e..0000000 --- a/helloworld/build.gradle +++ /dev/null @@ -1,4 +0,0 @@ -plugins { - id 'com.android.application' version '8.5.2' apply false - id 'org.jetbrains.kotlin.android' version '1.9.23' apply false -} \ No newline at end of file diff --git a/helloworld/gradle.properties b/helloworld/gradle.properties deleted file mode 100644 index 2d8d1e4..0000000 --- a/helloworld/gradle.properties +++ /dev/null @@ -1 +0,0 @@ -android.useAndroidX=true \ No newline at end of file diff --git a/helloworld/gradle/wrapper/gradle-wrapper.jar b/helloworld/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index e644113..0000000 Binary files a/helloworld/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/helloworld/gradle/wrapper/gradle-wrapper.properties b/helloworld/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index b82aa23..0000000 --- a/helloworld/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,7 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/helloworld/gradlew b/helloworld/gradlew deleted file mode 100755 index 1aa94a4..0000000 --- a/helloworld/gradlew +++ /dev/null @@ -1,249 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# 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. -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, -# and any embedded shellness will be escaped. -# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be -# treated as '${Hostname}' itself on the command line. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/helloworld/settings.gradle b/helloworld/settings.gradle deleted file mode 100644 index 28520ff..0000000 --- a/helloworld/settings.gradle +++ /dev/null @@ -1,17 +0,0 @@ -pluginManagement { - repositories { - google() - mavenCentral() - gradlePluginPortal() - } -} - -dependencyResolutionManagement { - repositories { - google() - mavenCentral() - } -} - -rootProject.name = "HelloWorld" -include ':app' \ No newline at end of file