├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro ├── schemas │ └── io.github.yamin8000.owl.db.AppDatabase │ │ └── 1.json └── src │ ├── free │ └── java │ │ └── io │ │ └── github │ │ └── yamin8000 │ │ └── owl │ │ └── content │ │ └── MainActivity.kt │ ├── irMarket │ ├── java │ │ └── io │ │ │ └── github │ │ │ └── yamin8000 │ │ │ └── owl │ │ │ ├── ad │ │ │ ├── AdComposables.kt │ │ │ └── AdHelper.kt │ │ │ └── content │ │ │ └── MainActivity.kt │ └── res │ │ └── layout │ │ └── standard_banner.xml │ └── main │ ├── AndroidManifest.xml │ ├── ic_launcher-playstore.png │ ├── java │ └── io │ │ └── github │ │ └── yamin8000 │ │ └── owl │ │ ├── content │ │ ├── About.kt │ │ ├── App.kt │ │ ├── CommonActivity.kt │ │ ├── MainBottomBar.kt │ │ ├── MainTopBar.kt │ │ ├── favourites │ │ │ ├── Favourites.kt │ │ │ └── FavouritesState.kt │ │ ├── history │ │ │ ├── History.kt │ │ │ └── HistoryState.kt │ │ ├── home │ │ │ ├── Home.kt │ │ │ ├── HomeComposables.kt │ │ │ └── HomeState.kt │ │ └── settings │ │ │ ├── Settings.kt │ │ │ ├── SettingsState.kt │ │ │ └── ThemeSetting.kt │ │ ├── db │ │ ├── AppDatabase.kt │ │ ├── dao │ │ │ ├── BaseDao.kt │ │ │ └── DAOs.kt │ │ └── entity │ │ │ ├── DefinitionEntity.kt │ │ │ └── WordEntity.kt │ │ ├── model │ │ ├── Definition.kt │ │ ├── RandomWord.kt │ │ └── Word.kt │ │ ├── network │ │ ├── APIs.kt │ │ └── Web.kt │ │ ├── ui │ │ ├── composable │ │ │ ├── Animations.kt │ │ │ ├── Cards.kt │ │ │ ├── Composables.kt │ │ │ └── Texts.kt │ │ ├── navigation │ │ │ └── Nav.kt │ │ ├── theme │ │ │ ├── Color.kt │ │ │ ├── Shape.kt │ │ │ ├── Theme.kt │ │ │ └── Type.kt │ │ └── util │ │ │ └── DynamicColorPalette.kt │ │ └── util │ │ ├── AutoCompleteHelper.kt │ │ ├── Constants.kt │ │ ├── DataStoreHelper.kt │ │ ├── RecomposeHighlighter.kt │ │ ├── TTS.kt │ │ ├── Utility.kt │ │ └── list │ │ └── ListSatiation.kt │ └── res │ ├── drawable │ ├── ic_gplv3.xml │ ├── ic_launcher_background.xml │ └── ic_launcher_foreground.xml │ ├── font │ └── samimbold.ttf │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── raw │ ├── basic2000.txt │ └── empty_list.json │ ├── values-fa │ └── strings.xml │ ├── values-hu │ └── strings.xml │ └── values │ ├── colors.xml │ ├── ic_launcher_background.xml │ ├── strings.xml │ └── themes.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── renovate.json ├── screenshots ├── 1.0.1 │ └── Screenshot_2022-08-24-02-51-48-048_io.github.yamin8000.owl.jpg ├── 1.0.4 │ ├── photo_2022-09-26_09-12-54.png │ └── photo_2022-09-26_09-12-55 (2).png └── 1.0.7 │ ├── photo_2022-11-10_22-27-32.jpg │ ├── photo_2022-11-10_22-27-33.jpg │ ├── photo_2022-11-10_22-27-34.jpg │ ├── photo_2022-11-10_22-27-36.jpg │ ├── photo_2022-11-10_22-27-37.jpg │ └── photo_2022-11-10_22-27-38.jpg └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | /.idea/inspectionProfiles/Project_Default.xml 17 | /.idea/.gitignore 18 | /.idea/.name 19 | /.idea/compiler.xml 20 | /.idea/gradle.xml 21 | /.idea/misc.xml 22 | /.idea/vcs.xml 23 | /.idea/ 24 | /app/src/main/java/io/github/yamin8000/owl/network/ApiKey.kt 25 | /app/release/ 26 | /app/src/irMarket/java/io/github/yamin8000/owl/ad/AdConstants.kt 27 | /app/irMarket/ 28 | /app/free/ 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Owl2 2 | 3 | ## Discontinued 4 | ## Since [Owlbot](https://owlbot.info/) API is unavailable development is stopped 5 | # Check the new version [here](https://github.com/yamin8000/freeDictionaryApp) 6 | 7 | 🦉 **Owl2/جغدک** is a simple android application for **OwlBot dictionary**. 8 | a reincarnation of https://github.com/yamin8000/Owl 9 | 10 | Owl2 is the first project in which I used **Jetpack Compose UI** and **Material3** color system. 11 | 12 | --- 13 | 14 | | Icon | Item | 15 | |:----:|:-----------------------------------:| 16 | | 📺 | [**Preview**](#Preview) | 17 | | 📱 | [**Compatibility**](#Compatibility) | 18 | | 💻 | [**Usage**](#Usage) | 19 | | 📩 | [**Download**](#Download) | 20 | | 📋 | [**Features**](#Features) | 21 | | ⚖️ | [**License**](#License) | 22 | 23 | --- 24 | 25 | ## Preview 26 | 27 | ### Featured on [TechDoc](https://www.youtube.com/watch?v=vlf0jEFHR74&t=59s) 28 | 29 | | Dark/Light Theme with Dynamic Color Content | Dark/Light Theme | 30 | |:----------------------------------------------------------------------------------------:|:--------------------------------------------------------------------------------------------:| 31 | | preview | preview | 32 | 33 | More screenshots [here](./screenshots). 34 | 35 | ## Compatibility 36 | 37 | **SDK21+** or **Android 5.0+** 38 | 39 | ## Usage 40 | 41 | Just use the search input to search the word. 42 | 43 | ## Download 44 | 45 | - GitHub releases: [here](https://github.com/yamin8000/Owl2/releases) 46 | - IzzyOnDroid: [here](https://apt.izzysoft.de/fdroid/index/apk/io.github.yamin8000.owl) 47 | - ~~Bazaar: [here](https://cafebazaar.ir/app/io.github.yamin8000.owl)~~ 48 | 49 | ## Features 50 | 51 | - English to English dictionary 52 | - Definition of the word 53 | - Example of the word usage if available 54 | - Emoji of the word if available 55 | - Picture of the word if available 56 | - Pronunciation of the word, both IPA text and audio using TTS 57 | - Save searched data for offline uses 58 | 59 | ## License 60 | 61 | > Owl is licensed under the **[GNU General Public License v3.0](./LICENSE)** 62 | > Permissions of this strong copyleft license are conditioned on making 63 | > available complete source code of licensed works and modifications, 64 | > which include larger works using a licensed work, under the same 65 | > license. Copyright and license notices must be preserved. Contributors 66 | > provide an express grant of patent rights. 67 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl: an android app for Owlbot Dictionary API 3 | * build.gradle Created by Yamin Siahmargooei at 2022/6/16 4 | * This file is part of Owl. 5 | * Copyright (C) 2022 Yamin Siahmargooei 6 | * 7 | * Owl is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * Owl is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Owl. If not, see . 19 | */ 20 | 21 | plugins { 22 | id 'com.android.application' 23 | id 'org.jetbrains.kotlin.android' 24 | id 'kotlin-parcelize' 25 | id 'com.google.devtools.ksp' version '1.8.22-1.0.11' 26 | } 27 | 28 | android { 29 | namespace 'io.github.yamin8000.owl' 30 | compileSdk 33 31 | 32 | defaultConfig { 33 | applicationId "io.github.yamin8000.owl" 34 | minSdk 21 35 | targetSdk 33 36 | versionCode 19 37 | versionName "1.3.6" 38 | vectorDrawables { 39 | useSupportLibrary true 40 | } 41 | ksp { 42 | arg("room.schemaLocation", "$projectDir/schemas".toString()) 43 | } 44 | buildConfigField("String", "OWLBOT_TOKEN", "\"${System.getenv("OWLBOT_TOKEN")}\"") 45 | buildConfigField("String", "API_NINJA_KEY", "\"${System.getenv("API_NINJA_KEY")}\"") 46 | } 47 | 48 | flavorDimensions "version" 49 | productFlavors { 50 | irMarket { 51 | dimension "version" 52 | } 53 | 54 | free { 55 | dimension "version" 56 | } 57 | } 58 | 59 | buildTypes { 60 | release { 61 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 62 | minifyEnabled true 63 | shrinkResources true 64 | } 65 | 66 | debug { 67 | minifyEnabled false 68 | shrinkResources false 69 | } 70 | } 71 | 72 | compileOptions { 73 | sourceCompatibility JavaVersion.VERSION_17 74 | targetCompatibility JavaVersion.VERSION_17 75 | } 76 | 77 | kotlinOptions { 78 | jvmTarget = '17' 79 | languageVersion = '1.9' 80 | } 81 | 82 | buildFeatures { 83 | compose true 84 | buildConfig true 85 | } 86 | 87 | composeOptions { 88 | kotlinCompilerExtensionVersion compose_compiler_version 89 | } 90 | 91 | packagingOptions { 92 | resources { 93 | excludes += '/META-INF/{AL2.0,LGPL2.1}' 94 | } 95 | } 96 | 97 | kotlin.sourceSets.all { 98 | languageSettings.enableLanguageFeature("DataObjects") 99 | } 100 | } 101 | 102 | dependencies { 103 | //core android 104 | implementation("androidx.core:core-ktx:1.10.1") 105 | implementation("androidx.palette:palette:1.0.0") 106 | //compose 107 | def material3_version = "1.1.1" 108 | implementation("androidx.compose.ui:ui:$compose_ui_libs_version") 109 | implementation("androidx.compose.material:material:$compose_libs_version") 110 | implementation("androidx.compose.ui:ui-tooling-preview:$compose_ui_libs_version") 111 | debugImplementation("androidx.compose.ui:ui-tooling:$compose_ui_libs_version") 112 | implementation("androidx.activity:activity-compose:1.7.2") 113 | implementation("androidx.compose.material:material-icons-extended:$compose_libs_version") 114 | implementation("androidx.compose.material3:material3:$material3_version") 115 | implementation("androidx.compose.material3:material3-window-size-class:$material3_version") 116 | //network 117 | def retrofit_version = "2.9.0" 118 | implementation("com.squareup.retrofit2:retrofit:$retrofit_version") 119 | implementation("com.squareup.retrofit2:converter-moshi:$retrofit_version") 120 | //coil 121 | def coil_version = "2.4.0" 122 | implementation("io.coil-kt:coil:$coil_version") 123 | implementation("io.coil-kt:coil-compose:$coil_version") 124 | //navigation 125 | def nav_version = "2.6.0" 126 | implementation("androidx.navigation:navigation-compose:$nav_version") 127 | //datastore 128 | implementation("androidx.datastore:datastore-preferences:1.0.0") 129 | //room 130 | def room_version = "2.5.2" 131 | implementation("androidx.room:room-runtime:$room_version") 132 | annotationProcessor("androidx.room:room-compiler:$room_version") 133 | ksp("androidx.room:room-compiler:$room_version") 134 | implementation("androidx.room:room-ktx:$room_version") 135 | //lottie 136 | implementation("com.airbnb.android:lottie-compose:6.0.1") 137 | //ad 138 | irMarketImplementation("ir.tapsell.plus:tapsell-plus-sdk-android:2.1.8") 139 | } -------------------------------------------------------------------------------- /app/schemas/io.github.yamin8000.owl.db.AppDatabase/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": 1, 3 | "database": { 4 | "version": 1, 5 | "identityHash": "0c3ad9bffebaa41fcb44752f733c24f4", 6 | "entities": [ 7 | { 8 | "tableName": "WordEntity", 9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `word` TEXT NOT NULL, `pronunciation` TEXT)", 10 | "fields": [ 11 | { 12 | "fieldPath": "id", 13 | "columnName": "id", 14 | "affinity": "INTEGER", 15 | "notNull": true 16 | }, 17 | { 18 | "fieldPath": "word", 19 | "columnName": "word", 20 | "affinity": "TEXT", 21 | "notNull": true 22 | }, 23 | { 24 | "fieldPath": "pronunciation", 25 | "columnName": "pronunciation", 26 | "affinity": "TEXT", 27 | "notNull": false 28 | } 29 | ], 30 | "primaryKey": { 31 | "columnNames": [ 32 | "id" 33 | ], 34 | "autoGenerate": true 35 | }, 36 | "indices": [], 37 | "foreignKeys": [] 38 | }, 39 | { 40 | "tableName": "DefinitionEntity", 41 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `wordId` INTEGER NOT NULL, `type` TEXT, `definition` TEXT NOT NULL, `example` TEXT, `imageUrl` TEXT, `emoji` TEXT, FOREIGN KEY(`wordId`) REFERENCES `WordEntity`(`id`) ON UPDATE NO ACTION ON DELETE RESTRICT )", 42 | "fields": [ 43 | { 44 | "fieldPath": "id", 45 | "columnName": "id", 46 | "affinity": "INTEGER", 47 | "notNull": true 48 | }, 49 | { 50 | "fieldPath": "wordId", 51 | "columnName": "wordId", 52 | "affinity": "INTEGER", 53 | "notNull": true 54 | }, 55 | { 56 | "fieldPath": "type", 57 | "columnName": "type", 58 | "affinity": "TEXT", 59 | "notNull": false 60 | }, 61 | { 62 | "fieldPath": "definition", 63 | "columnName": "definition", 64 | "affinity": "TEXT", 65 | "notNull": true 66 | }, 67 | { 68 | "fieldPath": "example", 69 | "columnName": "example", 70 | "affinity": "TEXT", 71 | "notNull": false 72 | }, 73 | { 74 | "fieldPath": "imageUrl", 75 | "columnName": "imageUrl", 76 | "affinity": "TEXT", 77 | "notNull": false 78 | }, 79 | { 80 | "fieldPath": "emoji", 81 | "columnName": "emoji", 82 | "affinity": "TEXT", 83 | "notNull": false 84 | } 85 | ], 86 | "primaryKey": { 87 | "columnNames": [ 88 | "id" 89 | ], 90 | "autoGenerate": true 91 | }, 92 | "indices": [ 93 | { 94 | "name": "index_DefinitionEntity_wordId", 95 | "unique": false, 96 | "columnNames": [ 97 | "wordId" 98 | ], 99 | "orders": [], 100 | "createSql": "CREATE INDEX IF NOT EXISTS `index_DefinitionEntity_wordId` ON `${TABLE_NAME}` (`wordId`)" 101 | } 102 | ], 103 | "foreignKeys": [ 104 | { 105 | "table": "WordEntity", 106 | "onDelete": "RESTRICT", 107 | "onUpdate": "NO ACTION", 108 | "columns": [ 109 | "wordId" 110 | ], 111 | "referencedColumns": [ 112 | "id" 113 | ] 114 | } 115 | ] 116 | } 117 | ], 118 | "views": [], 119 | "setupQueries": [ 120 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", 121 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '0c3ad9bffebaa41fcb44752f733c24f4')" 122 | ] 123 | } 124 | } -------------------------------------------------------------------------------- /app/src/free/java/io/github/yamin8000/owl/content/MainActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl: an android app for Owlbot Dictionary API 3 | * MainActivity.kt Created by Yamin Siahmargooei at 2022/9/20 4 | * This file is part of Owl. 5 | * Copyright (C) 2022 Yamin Siahmargooei 6 | * 7 | * Owl is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * Owl is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Owl. If not, see . 19 | */ 20 | 21 | package io.github.yamin8000.owl.content 22 | 23 | import android.annotation.SuppressLint 24 | import android.os.Bundle 25 | import androidx.activity.compose.setContent 26 | import androidx.compose.material3.ExperimentalMaterial3Api 27 | import androidx.compose.material3.Scaffold 28 | import androidx.compose.runtime.* 29 | import androidx.navigation.compose.NavHost 30 | import androidx.navigation.compose.rememberNavController 31 | import androidx.navigation.createGraph 32 | import io.github.yamin8000.owl.content.settings.ThemeSetting 33 | 34 | class MainActivity : CommonActivity() { 35 | 36 | @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") 37 | @ExperimentalMaterial3Api 38 | override fun onCreate(savedInstanceState: Bundle?) { 39 | super.onCreate(savedInstanceState) 40 | 41 | setContent { 42 | var currentTheme by remember { mutableStateOf(theme) } 43 | MainContent( 44 | currentTheme = currentTheme, 45 | content = { Scaffold { FreeMainContent { currentTheme = it } } } 46 | ) 47 | } 48 | } 49 | 50 | @Composable 51 | private fun FreeMainContent( 52 | onThemeChanged: (ThemeSetting) -> Unit 53 | ) { 54 | val navController = rememberNavController() 55 | val builder = mainNavigationGraph( 56 | navController = navController, 57 | outsideInput = outsideInput, 58 | onThemeChanged = onThemeChanged 59 | ) 60 | NavHost( 61 | navController = navController, 62 | graph = remember(startDestination, route, builder) { 63 | navController.createGraph(startDestination, route, builder) 64 | } 65 | ) 66 | } 67 | } -------------------------------------------------------------------------------- /app/src/irMarket/java/io/github/yamin8000/owl/ad/AdComposables.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl/Owl.app.main 3 | * AdComposables.kt Copyrighted by Yamin Siahmargooei at 2023/4/22 4 | * AdComposables.kt Last modified at 2023/4/22 5 | * This file is part of Owl/Owl.app.main. 6 | * Copyright (C) 2023 Yamin Siahmargooei 7 | * 8 | * Owl/Owl.app.main is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * Owl/Owl.app.main is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with Owl. If not, see . 20 | */ 21 | 22 | package io.github.yamin8000.owl.ad 23 | 24 | import android.view.LayoutInflater 25 | import android.view.ViewGroup 26 | import androidx.compose.material3.Surface 27 | import androidx.compose.runtime.Composable 28 | import androidx.compose.ui.Modifier 29 | import androidx.compose.ui.platform.LocalContext 30 | import androidx.compose.ui.viewinterop.AndroidView 31 | import io.github.yamin8000.owl.R 32 | 33 | @Composable 34 | fun TapsellAdContent( 35 | modifier: Modifier = Modifier, 36 | onCreated: (ViewGroup) -> Unit, 37 | onUpdate: (ViewGroup) -> Unit 38 | ) { 39 | val context = LocalContext.current 40 | Surface { 41 | AndroidView( 42 | modifier = modifier, 43 | update = onUpdate, 44 | factory = { 45 | val view = LayoutInflater.from(context) 46 | .inflate(R.layout.standard_banner, null, false) 47 | .findViewById(R.id.standardBanner) 48 | onCreated(view) 49 | view 50 | } 51 | ) 52 | } 53 | } -------------------------------------------------------------------------------- /app/src/irMarket/java/io/github/yamin8000/owl/ad/AdHelper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl/Owl.app.main 3 | * AdHelper.kt Copyrighted by Yamin Siahmargooei at 2023/4/22 4 | * AdHelper.kt Last modified at 2023/4/22 5 | * This file is part of Owl/Owl.app.main. 6 | * Copyright (C) 2023 Yamin Siahmargooei 7 | * 8 | * Owl/Owl.app.main is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * Owl/Owl.app.main is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with Owl. If not, see . 20 | */ 21 | 22 | package io.github.yamin8000.owl.ad 23 | 24 | import android.app.Activity 25 | import android.view.ViewGroup 26 | import io.github.yamin8000.owl.util.log 27 | import ir.tapsell.plus.AdRequestCallback 28 | import ir.tapsell.plus.AdShowListener 29 | import ir.tapsell.plus.TapsellPlus 30 | import ir.tapsell.plus.TapsellPlusBannerType 31 | import ir.tapsell.plus.model.TapsellPlusAdModel 32 | import ir.tapsell.plus.model.TapsellPlusErrorModel 33 | import kotlinx.coroutines.suspendCancellableCoroutine 34 | import kotlin.coroutines.resume 35 | import kotlin.coroutines.suspendCoroutine 36 | 37 | object AdHelper { 38 | 39 | suspend fun showTapsellAd( 40 | activity: Activity, 41 | adId: String, 42 | adView: ViewGroup? 43 | ) = suspendCoroutine { 44 | TapsellPlus.showStandardBannerAd( 45 | activity, 46 | adId, 47 | adView, 48 | object : AdShowListener() { 49 | override fun onOpened(tapsellPlusAdModel: TapsellPlusAdModel) { 50 | super.onOpened(tapsellPlusAdModel) 51 | log(tapsellPlusAdModel.responseId) 52 | } 53 | 54 | override fun onError(tapsellPlusErrorModel: TapsellPlusErrorModel) { 55 | super.onError(tapsellPlusErrorModel) 56 | log(tapsellPlusErrorModel.errorMessage) 57 | } 58 | }) 59 | } 60 | 61 | suspend fun requestTapsellAd( 62 | activity: Activity 63 | ) = suspendCancellableCoroutine { cancellableContinuation -> 64 | TapsellPlus.requestStandardBannerAd( 65 | activity, 66 | AdConstants.STANDARD_BANNER_ZONE_ID, 67 | TapsellPlusBannerType.BANNER_320x50, 68 | object : AdRequestCallback() { 69 | override fun response(ad: TapsellPlusAdModel?) { 70 | super.response(ad) 71 | cancellableContinuation.resume(ad?.responseId ?: "") 72 | } 73 | 74 | override fun error(error: String?) { 75 | super.error(error) 76 | log(error ?: "Unknown Tapsell Ad Request error") 77 | } 78 | } 79 | ) 80 | } 81 | } -------------------------------------------------------------------------------- /app/src/irMarket/java/io/github/yamin8000/owl/content/MainActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl/Owl.app.main 3 | * MainActivity.kt Copyrighted by Yamin Siahmargooei at 2023/4/22 4 | * MainActivity.kt Last modified at 2023/4/22 5 | * This file is part of Owl/Owl.app.main. 6 | * Copyright (C) 2023 Yamin Siahmargooei 7 | * 8 | * Owl/Owl.app.main is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * Owl/Owl.app.main is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with Owl. If not, see . 20 | */ 21 | 22 | package io.github.yamin8000.owl.content 23 | 24 | import android.annotation.SuppressLint 25 | import android.os.Bundle 26 | import android.view.ViewGroup 27 | import androidx.activity.compose.setContent 28 | import androidx.compose.foundation.layout.Column 29 | import androidx.compose.foundation.layout.fillMaxWidth 30 | import androidx.compose.foundation.layout.padding 31 | import androidx.compose.foundation.layout.wrapContentHeight 32 | import androidx.compose.material3.ExperimentalMaterial3Api 33 | import androidx.compose.material3.Scaffold 34 | import androidx.compose.runtime.Composable 35 | import androidx.compose.runtime.LaunchedEffect 36 | import androidx.compose.runtime.getValue 37 | import androidx.compose.runtime.mutableStateOf 38 | import androidx.compose.runtime.remember 39 | import androidx.compose.runtime.setValue 40 | import androidx.compose.ui.Modifier 41 | import androidx.compose.ui.unit.dp 42 | import androidx.navigation.compose.NavHost 43 | import androidx.navigation.compose.rememberNavController 44 | import androidx.navigation.createGraph 45 | import io.github.yamin8000.owl.ad.AdConstants 46 | import io.github.yamin8000.owl.ad.AdHelper 47 | import io.github.yamin8000.owl.ad.TapsellAdContent 48 | import io.github.yamin8000.owl.content.settings.ThemeSetting 49 | import io.github.yamin8000.owl.util.log 50 | import ir.tapsell.plus.TapsellPlus 51 | import ir.tapsell.plus.TapsellPlusInitListener 52 | import ir.tapsell.plus.model.AdNetworkError 53 | import ir.tapsell.plus.model.AdNetworks 54 | 55 | class MainActivity : CommonActivity() { 56 | @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") 57 | @ExperimentalMaterial3Api 58 | override fun onCreate(savedInstanceState: Bundle?) { 59 | super.onCreate(savedInstanceState) 60 | 61 | initTapsellAd() 62 | setContent { 63 | var currentTheme by remember { mutableStateOf(theme) } 64 | MainContent( 65 | currentTheme = currentTheme, 66 | content = { 67 | var adView by remember { mutableStateOf(null) } 68 | var adId: String by remember { mutableStateOf("") } 69 | 70 | LaunchedEffect(Unit) { 71 | adId = AdHelper.requestTapsellAd(this@MainActivity) 72 | AdHelper.showTapsellAd(this@MainActivity, adId, adView) 73 | } 74 | Scaffold { 75 | AdMainContent( 76 | onCreated = { adView = it }, 77 | onUpdate = { adView = it }, 78 | onThemeChanged = { currentTheme = it } 79 | ) 80 | } 81 | } 82 | ) 83 | } 84 | } 85 | 86 | private fun initTapsellAd() { 87 | TapsellPlus.initialize(this, AdConstants.TAPSELL_KEY, object : TapsellPlusInitListener { 88 | override fun onInitializeSuccess(ads: AdNetworks?) { 89 | log(ads?.name ?: "Unknown ad name") 90 | } 91 | 92 | override fun onInitializeFailed(ads: AdNetworks?, error: AdNetworkError?) { 93 | log(error?.errorMessage ?: "Unknown tapsell init error") 94 | } 95 | }) 96 | } 97 | 98 | @Composable 99 | private fun AdMainContent( 100 | onCreated: (ViewGroup) -> Unit, 101 | onUpdate: (ViewGroup) -> Unit, 102 | onThemeChanged: (ThemeSetting) -> Unit 103 | ) { 104 | Column { 105 | val navController = rememberNavController() 106 | val builder = mainNavigationGraph( 107 | navController = navController, 108 | outsideInput = outsideInput, 109 | onThemeChanged = onThemeChanged 110 | ) 111 | NavHost( 112 | modifier = Modifier.weight(1f), 113 | navController = navController, 114 | graph = remember(startDestination, route, builder) { 115 | navController.createGraph(startDestination, route, builder) 116 | } 117 | ) 118 | TapsellAdContent( 119 | modifier = Modifier 120 | .wrapContentHeight() 121 | .padding(4.dp) 122 | .fillMaxWidth(), 123 | onCreated = onCreated, 124 | onUpdate = onUpdate 125 | ) 126 | } 127 | } 128 | } -------------------------------------------------------------------------------- /app/src/irMarket/res/layout/standard_banner.xml: -------------------------------------------------------------------------------- 1 | 21 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 33 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yamin8000/Owl2/abf80da26053409758c41c5d2b4e88116d42fba4/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/io/github/yamin8000/owl/content/About.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl: an android app for Owlbot Dictionary API 3 | * About.kt Created by Yamin Siahmargooei at 2022/9/19 4 | * This file is part of Owl. 5 | * Copyright (C) 2022 Yamin Siahmargooei 6 | * 7 | * Owl is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * Owl is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Owl. If not, see . 19 | */ 20 | 21 | package io.github.yamin8000.owl.content 22 | 23 | import androidx.compose.foundation.Image 24 | import androidx.compose.foundation.layout.* 25 | import androidx.compose.foundation.rememberScrollState 26 | import androidx.compose.foundation.verticalScroll 27 | import androidx.compose.material3.ExperimentalMaterial3Api 28 | import androidx.compose.material3.MaterialTheme 29 | import androidx.compose.material3.Text 30 | import androidx.compose.runtime.Composable 31 | import androidx.compose.ui.Alignment 32 | import androidx.compose.ui.Modifier 33 | import androidx.compose.ui.graphics.ColorFilter 34 | import androidx.compose.ui.layout.ContentScale 35 | import androidx.compose.ui.platform.LocalUriHandler 36 | import androidx.compose.ui.res.painterResource 37 | import androidx.compose.ui.res.stringResource 38 | import androidx.compose.ui.text.style.TextDecoration 39 | import androidx.compose.ui.unit.dp 40 | import io.github.yamin8000.owl.BuildConfig 41 | import io.github.yamin8000.owl.R 42 | import io.github.yamin8000.owl.ui.composable.PersianText 43 | import io.github.yamin8000.owl.ui.composable.Ripple 44 | import io.github.yamin8000.owl.ui.composable.ScaffoldWithTitle 45 | 46 | @OptIn(ExperimentalMaterial3Api::class) 47 | @Composable 48 | fun AboutContent( 49 | onBackClick: () -> Unit 50 | ) { 51 | ScaffoldWithTitle( 52 | title = stringResource(R.string.about), 53 | onBackClick = onBackClick, 54 | content = { 55 | Column( 56 | modifier = Modifier.verticalScroll(rememberScrollState()), 57 | verticalArrangement = Arrangement.spacedBy(8.dp), 58 | content = { 59 | val uriHandler = LocalUriHandler.current 60 | val sourceUri = stringResource(R.string.github_source) 61 | val owlBotUri = stringResource(R.string.owl_bot_link) 62 | val licenseUri = stringResource(R.string.license_link) 63 | Ripple( 64 | onClick = { uriHandler.openUri(licenseUri) }, 65 | content = { 66 | Image( 67 | painterResource(id = R.drawable.ic_gplv3), 68 | stringResource(id = R.string.gplv3_image_description), 69 | modifier = Modifier 70 | .padding(32.dp) 71 | .fillMaxWidth(), 72 | contentScale = ContentScale.FillWidth, 73 | colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurface) 74 | ) 75 | } 76 | ) 77 | Row( 78 | modifier = Modifier.fillMaxWidth(), 79 | verticalAlignment = Alignment.CenterVertically, 80 | horizontalArrangement = Arrangement.spacedBy( 81 | 8.dp, 82 | Alignment.CenterHorizontally 83 | ), 84 | content = { 85 | PersianText(stringResource(R.string.version_name)) 86 | PersianText(BuildConfig.VERSION_NAME) 87 | } 88 | ) 89 | PersianText( 90 | text = stringResource(id = R.string.license_header), 91 | modifier = Modifier.fillMaxWidth() 92 | ) 93 | Ripple( 94 | onClick = { uriHandler.openUri(sourceUri) }, 95 | content = { 96 | Text( 97 | text = sourceUri, 98 | textDecoration = TextDecoration.Underline 99 | ) 100 | } 101 | ) 102 | PersianText( 103 | text = stringResource(id = R.string.about_app), 104 | modifier = Modifier.fillMaxWidth() 105 | ) 106 | Ripple( 107 | onClick = { uriHandler.openUri(owlBotUri) }, 108 | content = { 109 | Text( 110 | text = owlBotUri, 111 | textDecoration = TextDecoration.Underline 112 | ) 113 | } 114 | ) 115 | } 116 | ) 117 | } 118 | ) 119 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/yamin8000/owl/content/App.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl/Owl.app.main 3 | * App.kt Copyrighted by Yamin Siahmargooei at 2023/4/22 4 | * App.kt Last modified at 2023/4/22 5 | * This file is part of Owl/Owl.app.main. 6 | * Copyright (C) 2023 Yamin Siahmargooei 7 | * 8 | * Owl/Owl.app.main is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * Owl/Owl.app.main is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with Owl. If not, see . 20 | */ 21 | 22 | package io.github.yamin8000.owl.content 23 | 24 | import android.app.Application 25 | import android.content.Context 26 | import androidx.datastore.core.DataStore 27 | import androidx.datastore.preferences.core.Preferences 28 | import androidx.datastore.preferences.preferencesDataStore 29 | 30 | val Context.settingsDataStore: DataStore by preferencesDataStore(name = "settings") 31 | val Context.historyDataStore: DataStore by preferencesDataStore(name = "history") 32 | val Context.favouritesDataStore: DataStore by preferencesDataStore(name = "favourites") 33 | 34 | class App : Application() -------------------------------------------------------------------------------- /app/src/main/java/io/github/yamin8000/owl/content/CommonActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl2/Owl2.app.main 3 | * MainActivity.kt Copyrighted by Yamin Siahmargooei at 2023/5/18 4 | * MainActivity.kt Last modified at 2023/5/18 5 | * This file is part of Owl2/Owl2.app.main. 6 | * Copyright (C) 2023 Yamin Siahmargooei 7 | * 8 | * Owl2/Owl2.app.main is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * Owl2/Owl2.app.main is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with Owl2. If not, see . 20 | */ 21 | 22 | package io.github.yamin8000.owl.content 23 | 24 | import android.content.Intent 25 | import android.os.Build 26 | import android.os.Bundle 27 | import androidx.activity.ComponentActivity 28 | import androidx.compose.foundation.isSystemInDarkTheme 29 | import androidx.compose.runtime.Composable 30 | import androidx.navigation.NavGraphBuilder 31 | import androidx.navigation.NavHostController 32 | import androidx.navigation.compose.composable 33 | import androidx.room.Room 34 | import io.github.yamin8000.owl.content.favourites.FavouritesContent 35 | import io.github.yamin8000.owl.content.history.HistoryContent 36 | import io.github.yamin8000.owl.content.home.HomeContent 37 | import io.github.yamin8000.owl.content.settings.SettingsContent 38 | import io.github.yamin8000.owl.content.settings.ThemeSetting 39 | import io.github.yamin8000.owl.db.AppDatabase 40 | import io.github.yamin8000.owl.ui.navigation.Nav 41 | import io.github.yamin8000.owl.ui.theme.OwlTheme 42 | import io.github.yamin8000.owl.util.Constants 43 | import io.github.yamin8000.owl.util.DataStoreHelper 44 | import io.github.yamin8000.owl.util.log 45 | import kotlinx.coroutines.runBlocking 46 | 47 | abstract class CommonActivity : ComponentActivity() { 48 | 49 | protected var outsideInput: String? = null 50 | 51 | protected var theme: ThemeSetting = ThemeSetting.System 52 | 53 | protected val startDestination = "${Nav.Routes.Home}/{${Nav.Arguments.Search}}" 54 | 55 | protected val route: String? = null 56 | 57 | override fun onCreate(savedInstanceState: Bundle?) { 58 | super.onCreate(savedInstanceState) 59 | 60 | Constants.db = createDb() 61 | outsideInput = handleOutsideInputIntent() 62 | try { 63 | runBlocking { theme = getCurrentTheme() } 64 | } catch (e: InterruptedException) { 65 | log(e.stackTraceToString()) 66 | } 67 | } 68 | 69 | @Composable 70 | protected fun MainContent( 71 | currentTheme: ThemeSetting, 72 | content: @Composable () -> Unit 73 | ) { 74 | OwlTheme( 75 | isDarkTheme = isDarkTheme(currentTheme, isSystemInDarkTheme()), 76 | isDynamicColor = currentTheme == ThemeSetting.System, 77 | content = content 78 | ) 79 | } 80 | 81 | protected fun mainNavigationGraph( 82 | navController: NavHostController, 83 | outsideInput: String?, 84 | onThemeChanged: (ThemeSetting) -> Unit 85 | ): NavGraphBuilder.() -> Unit = { 86 | composable("${Nav.Routes.Home}/{${Nav.Arguments.Search}}") { 87 | var searchTerm = it.arguments?.getString(Nav.Arguments.Search.toString()) 88 | if (searchTerm == null && outsideInput != null) 89 | searchTerm = outsideInput.toString() 90 | HomeContent( 91 | searchTerm = searchTerm, 92 | onFavouritesClick = { navController.navigate(Nav.Routes.Favourites.toString()) }, 93 | onHistoryClick = { navController.navigate(Nav.Routes.History.toString()) }, 94 | onInfoClick = { navController.navigate(Nav.Routes.About.toString()) }, 95 | onSettingsClick = { navController.navigate(Nav.Routes.Settings.toString()) } 96 | ) 97 | } 98 | 99 | composable(Nav.Routes.About.toString()) { 100 | AboutContent { navController.popBackStack() } 101 | } 102 | 103 | composable(Nav.Routes.Favourites.toString()) { 104 | FavouritesContent( 105 | onFavouritesItemClick = { favourite -> navController.navigate("${Nav.Routes.Home}/${favourite}") }, 106 | onBackClick = { navController.popBackStack() } 107 | ) 108 | } 109 | 110 | composable(Nav.Routes.History.toString()) { 111 | HistoryContent( 112 | onHistoryItemClick = { history -> navController.navigate("${Nav.Routes.Home}/${history}") }, 113 | onBackClick = { navController.popBackStack() } 114 | ) 115 | } 116 | 117 | composable(Nav.Routes.Settings.toString()) { 118 | SettingsContent( 119 | onThemeChanged = onThemeChanged, 120 | onBackClick = { navController.popBackStack() } 121 | ) 122 | } 123 | } 124 | 125 | private fun isDarkTheme( 126 | themeSetting: ThemeSetting, 127 | isSystemInDarkTheme: Boolean 128 | ): Boolean { 129 | if (themeSetting == ThemeSetting.Light) return false 130 | if (themeSetting == ThemeSetting.System) return isSystemInDarkTheme 131 | if (themeSetting == ThemeSetting.Dark) return true 132 | return false 133 | } 134 | 135 | private fun createDb() = Room.databaseBuilder( 136 | this, 137 | AppDatabase::class.java, 138 | "db" 139 | ).build() 140 | 141 | private fun handleOutsideInputIntent(): String? { 142 | return if (intent.type == "text/plain") { 143 | when (intent.action) { 144 | Intent.ACTION_TRANSLATE, Intent.ACTION_DEFINE, Intent.ACTION_SEND -> { 145 | intent.getStringExtra(Intent.EXTRA_TEXT) 146 | } 147 | 148 | Intent.ACTION_PROCESS_TEXT -> { 149 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) 150 | intent.getStringExtra(Intent.EXTRA_PROCESS_TEXT) 151 | else null 152 | } 153 | 154 | else -> null 155 | } 156 | } else null 157 | } 158 | 159 | private suspend fun getCurrentTheme() = ThemeSetting.valueOf( 160 | DataStoreHelper(settingsDataStore).getString(Constants.THEME) ?: ThemeSetting.System.name 161 | ) 162 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/yamin8000/owl/content/MainBottomBar.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl: an android app for Owlbot Dictionary API 3 | * MainBottomBar.kt Created by Yamin Siahmargooei at 2022/9/19 4 | * This file is part of Owl. 5 | * Copyright (C) 2022 Yamin Siahmargooei 6 | * 7 | * Owl is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * Owl is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Owl. If not, see . 19 | */ 20 | 21 | package io.github.yamin8000.owl.content 22 | 23 | import android.content.Context 24 | import androidx.compose.animation.Crossfade 25 | import androidx.compose.foundation.layout.Arrangement 26 | import androidx.compose.foundation.layout.Column 27 | import androidx.compose.foundation.layout.fillMaxWidth 28 | import androidx.compose.foundation.layout.padding 29 | import androidx.compose.foundation.lazy.LazyRow 30 | import androidx.compose.foundation.lazy.items 31 | import androidx.compose.foundation.shape.CutCornerShape 32 | import androidx.compose.foundation.text.KeyboardActions 33 | import androidx.compose.foundation.text.KeyboardOptions 34 | import androidx.compose.material.icons.Icons 35 | import androidx.compose.material.icons.twotone.Clear 36 | import androidx.compose.material.icons.twotone.Search 37 | import androidx.compose.material.icons.twotone.Stop 38 | import androidx.compose.material3.* 39 | import androidx.compose.runtime.* 40 | import androidx.compose.ui.Modifier 41 | import androidx.compose.ui.platform.LocalContext 42 | import androidx.compose.ui.res.stringResource 43 | import androidx.compose.ui.text.TextStyle 44 | import androidx.compose.ui.text.input.ImeAction 45 | import androidx.compose.ui.text.input.KeyboardCapitalization 46 | import androidx.compose.ui.text.input.KeyboardType 47 | import androidx.compose.ui.text.style.TextAlign 48 | import androidx.compose.ui.text.style.TextDirection 49 | import androidx.compose.ui.unit.dp 50 | import androidx.compose.ui.unit.sp 51 | import io.github.yamin8000.owl.R 52 | import io.github.yamin8000.owl.ui.composable.ClickableIcon 53 | import io.github.yamin8000.owl.ui.composable.HighlightText 54 | import io.github.yamin8000.owl.ui.composable.PersianText 55 | import io.github.yamin8000.owl.ui.theme.Samim 56 | import io.github.yamin8000.owl.util.ImmutableHolder 57 | import io.github.yamin8000.owl.util.getCurrentLocale 58 | import kotlinx.coroutines.delay 59 | import java.util.* 60 | 61 | @OptIn(ExperimentalMaterial3Api::class) 62 | @Composable 63 | fun MainBottomBar( 64 | searchTerm: String?, 65 | suggestions: ImmutableHolder>, 66 | onSuggestionClick: (String) -> Unit, 67 | isSearching: Boolean, 68 | onSearchTermChanged: (String) -> Unit, 69 | onSearch: (String) -> Unit, 70 | onCancel: () -> Unit 71 | ) { 72 | var searchText by remember { mutableStateOf(searchTerm ?: "") } 73 | Column { 74 | if (suggestions.item.isNotEmpty()) { 75 | LazyRow( 76 | modifier = Modifier.padding(8.dp), 77 | horizontalArrangement = Arrangement.spacedBy(4.dp), 78 | content = { 79 | items( 80 | items = suggestions.item, 81 | itemContent = { 82 | ElevatedSuggestionChip( 83 | label = { HighlightText(it, searchText) }, 84 | onClick = { 85 | onSuggestionClick(it) 86 | searchText = it 87 | } 88 | ) 89 | } 90 | ) 91 | } 92 | ) 93 | } 94 | if (isSearching) 95 | RainbowLinearProgress() 96 | Crossfade( 97 | targetState = isSearching, 98 | content = { target -> 99 | if (target) { 100 | BottomAppBar( 101 | actions = {}, 102 | floatingActionButton = { 103 | FloatingActionButton( 104 | onClick = onCancel, 105 | content = { 106 | Icon( 107 | imageVector = Icons.TwoTone.Stop, 108 | contentDescription = stringResource(R.string.cancel) 109 | ) 110 | } 111 | ) 112 | } 113 | ) 114 | } else { 115 | BottomAppBar { 116 | TextField( 117 | singleLine = true, 118 | shape = CutCornerShape(topEnd = 10.dp, topStart = 10.dp), 119 | modifier = Modifier 120 | .fillMaxWidth() 121 | .padding(16.dp, 0.dp, 16.dp, 0.dp), 122 | label = { 123 | PersianText( 124 | stringResource(R.string.search), 125 | modifier = Modifier.fillMaxWidth() 126 | ) 127 | }, 128 | placeholder = { 129 | PersianText( 130 | text = stringResource(R.string.search_hint), 131 | modifier = Modifier.fillMaxWidth(), 132 | fontSize = 12.sp 133 | ) 134 | }, 135 | leadingIcon = { 136 | ClickableIcon( 137 | imageVector = Icons.TwoTone.Clear, 138 | contentDescription = stringResource(R.string.clear), 139 | onClick = { searchText = "" } 140 | ) 141 | }, 142 | trailingIcon = { 143 | ClickableIcon( 144 | imageVector = Icons.TwoTone.Search, 145 | contentDescription = stringResource(R.string.search), 146 | onClick = { onSearch(searchText) } 147 | ) 148 | }, 149 | value = searchText, 150 | onValueChange = { 151 | searchText = it 152 | onSearchTermChanged(searchText) 153 | }, 154 | textStyle = getTextStyleBasedOnLocale(LocalContext.current), 155 | keyboardActions = KeyboardActions(onSearch = { onSearch(searchText) }), 156 | keyboardOptions = KeyboardOptions( 157 | imeAction = ImeAction.Search, 158 | keyboardType = KeyboardType.Text, 159 | capitalization = KeyboardCapitalization.Words 160 | ) 161 | ) 162 | } 163 | } 164 | } 165 | ) 166 | } 167 | } 168 | 169 | @Composable 170 | private fun RainbowLinearProgress() { 171 | fun randomBeam(): Int = (16..255).random() 172 | val colors = buildList { 173 | repeat((5..10).random()) { 174 | add(androidx.compose.ui.graphics.Color(randomBeam(), randomBeam(), randomBeam())) 175 | } 176 | } 177 | var color by remember { mutableStateOf(colors.first()) } 178 | LaunchedEffect(Unit) { 179 | while (true) { 180 | color = colors.random() 181 | delay(250) 182 | } 183 | } 184 | LinearProgressIndicator( 185 | modifier = Modifier.fillMaxWidth(), 186 | color = color 187 | ) 188 | } 189 | 190 | private fun getTextStyleBasedOnLocale( 191 | context: Context 192 | ): TextStyle { 193 | return if (getCurrentLocale(context).language == Locale("fa").language) { 194 | TextStyle( 195 | fontFamily = Samim, 196 | textAlign = TextAlign.Right, 197 | textDirection = TextDirection.Rtl 198 | ) 199 | } else TextStyle() 200 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/yamin8000/owl/content/MainTopBar.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl: an android app for Owlbot Dictionary API 3 | * MainTopBar.kt Created by Yamin Siahmargooei at 2022/9/19 4 | * This file is part of Owl. 5 | * Copyright (C) 2022 Yamin Siahmargooei 6 | * 7 | * Owl is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * Owl is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Owl. If not, see . 19 | */ 20 | 21 | package io.github.yamin8000.owl.content 22 | 23 | import androidx.compose.material.icons.Icons 24 | import androidx.compose.material.icons.twotone.* 25 | import androidx.compose.material3.* 26 | import androidx.compose.runtime.Composable 27 | import androidx.compose.ui.res.stringResource 28 | import androidx.compose.ui.unit.dp 29 | import io.github.yamin8000.owl.R 30 | import io.github.yamin8000.owl.ui.composable.AnimatedAppIcon 31 | import io.github.yamin8000.owl.ui.composable.ClickableIcon 32 | 33 | @OptIn(ExperimentalMaterial3Api::class) 34 | @Composable 35 | fun MainTopBar( 36 | scrollBehavior: TopAppBarScrollBehavior, 37 | onHistoryClick: () -> Unit, 38 | onFavouritesClick: () -> Unit, 39 | onRandomWordClick: () -> Unit, 40 | onSettingsClick: () -> Unit, 41 | onInfoClick: () -> Unit, 42 | ) { 43 | Surface( 44 | shadowElevation = 8.dp, 45 | content = { 46 | TopAppBar( 47 | scrollBehavior = scrollBehavior, 48 | title = { AnimatedAppIcon() }, 49 | actions = { 50 | ClickableIcon( 51 | imageVector = Icons.TwoTone.History, 52 | contentDescription = stringResource(R.string.search_history), 53 | onClick = onHistoryClick, 54 | ) 55 | 56 | ClickableIcon( 57 | imageVector = Icons.TwoTone.Favorite, 58 | contentDescription = stringResource(R.string.favourites), 59 | onClick = onFavouritesClick, 60 | ) 61 | 62 | ClickableIcon( 63 | imageVector = Icons.TwoTone.Casino, 64 | contentDescription = stringResource(R.string.random_word), 65 | onClick = onRandomWordClick, 66 | ) 67 | 68 | ClickableIcon( 69 | imageVector = Icons.TwoTone.Settings, 70 | contentDescription = stringResource(R.string.settings), 71 | onClick = onSettingsClick 72 | ) 73 | 74 | ClickableIcon( 75 | imageVector = Icons.TwoTone.Info, 76 | contentDescription = stringResource(R.string.about_app), 77 | onClick = onInfoClick 78 | ) 79 | } 80 | ) 81 | } 82 | ) 83 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/yamin8000/owl/content/favourites/Favourites.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl: an android app for Owlbot Dictionary API 3 | * Favourites.kt Created by Yamin Siahmargooei at 2022/8/22 4 | * This file is part of Owl. 5 | * Copyright (C) 2022 Yamin Siahmargooei 6 | * 7 | * Owl is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * Owl is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Owl. If not, see . 19 | */ 20 | 21 | package io.github.yamin8000.owl.content.favourites 22 | 23 | import androidx.compose.foundation.layout.Arrangement 24 | import androidx.compose.foundation.layout.fillMaxWidth 25 | import androidx.compose.foundation.lazy.grid.GridCells 26 | import androidx.compose.foundation.lazy.grid.LazyVerticalGrid 27 | import androidx.compose.foundation.lazy.grid.items 28 | import androidx.compose.material3.ExperimentalMaterial3Api 29 | import androidx.compose.runtime.Composable 30 | import androidx.compose.runtime.saveable.rememberSaveable 31 | import androidx.compose.ui.Modifier 32 | import androidx.compose.ui.res.stringResource 33 | import androidx.compose.ui.unit.dp 34 | import io.github.yamin8000.owl.R 35 | import io.github.yamin8000.owl.ui.composable.EmptyList 36 | import io.github.yamin8000.owl.ui.composable.RemovableCard 37 | import io.github.yamin8000.owl.ui.composable.ScaffoldWithTitle 38 | import io.github.yamin8000.owl.util.list.ListSatiation 39 | import kotlinx.coroutines.launch 40 | 41 | @OptIn(ExperimentalMaterial3Api::class) 42 | @Composable 43 | fun FavouritesContent( 44 | onFavouritesItemClick: (String) -> Unit, 45 | onBackClick: () -> Unit 46 | ) { 47 | val state = rememberFavouritesState() 48 | 49 | ScaffoldWithTitle( 50 | title = stringResource(R.string.favourites), 51 | onBackClick = onBackClick 52 | ) { 53 | when (state.listSatiation) { 54 | ListSatiation.Empty -> EmptyList() 55 | ListSatiation.Partial -> { 56 | FavouritesGrid( 57 | favourites = state.favourites.value.toList(), 58 | onItemClick = onFavouritesItemClick, 59 | onItemLongClick = { favourite -> 60 | state.scope.launch { state.removeFavourite(favourite) } 61 | } 62 | ) 63 | } 64 | } 65 | } 66 | } 67 | 68 | @Composable 69 | fun FavouritesGrid( 70 | favourites: List, 71 | onItemClick: (String) -> Unit, 72 | onItemLongClick: (String) -> Unit 73 | ) { 74 | val span = rememberSaveable { if (favourites.size == 1) 1 else 2 } 75 | LazyVerticalGrid( 76 | modifier = Modifier.fillMaxWidth(), 77 | horizontalArrangement = Arrangement.spacedBy(8.dp), 78 | verticalArrangement = Arrangement.spacedBy(8.dp), 79 | columns = GridCells.Fixed(span), 80 | content = { 81 | items( 82 | items = favourites, 83 | itemContent = { favourite -> 84 | FavouriteItem( 85 | favourite = favourite, 86 | onClick = onItemClick, 87 | onLongClick = { onItemLongClick(favourite) } 88 | ) 89 | } 90 | ) 91 | } 92 | ) 93 | } 94 | 95 | @Composable 96 | private fun FavouriteItem( 97 | favourite: String, 98 | onClick: (String) -> Unit, 99 | onLongClick: () -> Unit 100 | ) { 101 | RemovableCard( 102 | item = favourite, 103 | onClick = { onClick(favourite) }, 104 | onLongClick = onLongClick 105 | ) 106 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/yamin8000/owl/content/favourites/FavouritesState.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl: an android app for Owlbot Dictionary API 3 | * FavouritesProvider.kt Created by Yamin Siahmargooei at 2022/8/21 4 | * This file is part of Owl. 5 | * Copyright (C) 2022 Yamin Siahmargooei 6 | * 7 | * Owl is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * Owl is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Owl. If not, see . 19 | */ 20 | 21 | package io.github.yamin8000.owl.content.favourites 22 | 23 | import android.content.Context 24 | import androidx.compose.runtime.Composable 25 | import androidx.compose.runtime.MutableState 26 | import androidx.compose.runtime.mutableStateOf 27 | import androidx.compose.runtime.remember 28 | import androidx.compose.runtime.saveable.rememberSaveable 29 | import androidx.compose.ui.platform.LocalContext 30 | import androidx.compose.ui.platform.LocalLifecycleOwner 31 | import androidx.datastore.preferences.core.edit 32 | import androidx.datastore.preferences.core.stringPreferencesKey 33 | import androidx.lifecycle.LifecycleCoroutineScope 34 | import androidx.lifecycle.lifecycleScope 35 | import io.github.yamin8000.owl.content.favouritesDataStore 36 | import io.github.yamin8000.owl.util.list.ListSatiation 37 | import kotlinx.coroutines.launch 38 | 39 | class FavouritesState( 40 | private val context: Context, 41 | val scope: LifecycleCoroutineScope, 42 | var favourites: MutableState> 43 | ) { 44 | 45 | init { 46 | scope.launch { fetchFavourites() } 47 | } 48 | 49 | val listSatiation: ListSatiation 50 | get() { 51 | return when (favourites.value.size) { 52 | 0 -> ListSatiation.Empty 53 | else -> ListSatiation.Partial 54 | } 55 | } 56 | 57 | private suspend fun fetchFavourites() { 58 | context.favouritesDataStore.data.collect { preferences -> 59 | val newSet = favourites.value.toMutableSet() 60 | preferences.asMap().forEach { entry -> newSet.add(entry.value.toString()) } 61 | favourites.value = newSet 62 | } 63 | } 64 | 65 | suspend fun removeFavourite( 66 | favourite: String 67 | ) { 68 | context.favouritesDataStore.edit { it.remove(stringPreferencesKey(favourite)) } 69 | val newSet = favourites.value.toMutableSet() 70 | newSet.remove(favourite) 71 | favourites.value = newSet 72 | } 73 | } 74 | 75 | @Composable 76 | fun rememberFavouritesState( 77 | context: Context = LocalContext.current, 78 | lifeCycleScope: LifecycleCoroutineScope = LocalLifecycleOwner.current.lifecycleScope, 79 | favourites: MutableState> = rememberSaveable { mutableStateOf(emptySet()) } 80 | ) = remember(context) { FavouritesState(context, lifeCycleScope, favourites) } -------------------------------------------------------------------------------- /app/src/main/java/io/github/yamin8000/owl/content/history/History.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl: an android app for Owlbot Dictionary API 3 | * History.kt Created by Yamin Siahmargooei at 2022/8/24 4 | * This file is part of Owl. 5 | * Copyright (C) 2022 Yamin Siahmargooei 6 | * 7 | * Owl is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * Owl is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Owl. If not, see . 19 | */ 20 | 21 | package io.github.yamin8000.owl.content.history 22 | 23 | import androidx.compose.foundation.layout.Arrangement 24 | import androidx.compose.foundation.lazy.grid.GridCells 25 | import androidx.compose.foundation.lazy.grid.GridItemSpan 26 | import androidx.compose.foundation.lazy.grid.LazyVerticalGrid 27 | import androidx.compose.material3.Button 28 | import androidx.compose.material3.ExperimentalMaterial3Api 29 | import androidx.compose.runtime.Composable 30 | import androidx.compose.ui.res.stringResource 31 | import androidx.compose.ui.unit.dp 32 | import io.github.yamin8000.owl.R 33 | import io.github.yamin8000.owl.ui.composable.* 34 | import io.github.yamin8000.owl.ui.theme.DefaultCutShape 35 | import io.github.yamin8000.owl.util.list.ListSatiation 36 | import kotlinx.coroutines.launch 37 | 38 | @OptIn(ExperimentalMaterial3Api::class) 39 | @Composable 40 | fun HistoryContent( 41 | onHistoryItemClick: (String) -> Unit, 42 | onBackClick: () -> Unit 43 | ) { 44 | val state = rememberHistoryState() 45 | 46 | ScaffoldWithTitle( 47 | title = stringResource(R.string.search_history), 48 | onBackClick = onBackClick, 49 | content = { 50 | when (state.listSatiation) { 51 | ListSatiation.Empty -> EmptyList() 52 | ListSatiation.Partial -> { 53 | val list = state.history.value.toList() 54 | LazyVerticalGrid( 55 | horizontalArrangement = Arrangement.spacedBy(8.dp), 56 | verticalArrangement = Arrangement.spacedBy(8.dp), 57 | columns = GridCells.Fixed(2), 58 | content = { 59 | item( 60 | span = { GridItemSpan(maxCurrentLineSpan) }, 61 | content = { 62 | RemoveAlHistoryButton { 63 | state.lifeCycleScope.launch { state.removeAllHistory() } 64 | } 65 | } 66 | ) 67 | items( 68 | span = { GridItemSpan(1) }, 69 | count = list.size, 70 | itemContent = { 71 | HistoryItem( 72 | history = list[it], 73 | onClick = onHistoryItemClick, 74 | onLongClick = { 75 | state.lifeCycleScope.launch { 76 | state.removeSingleHistory(it) 77 | } 78 | } 79 | ) 80 | } 81 | ) 82 | } 83 | ) 84 | } 85 | } 86 | } 87 | ) 88 | } 89 | 90 | @Composable 91 | private fun RemoveAlHistoryButton( 92 | onRemoveAllClick: () -> Unit 93 | ) { 94 | Button( 95 | onClick = onRemoveAllClick, 96 | shape = DefaultCutShape, 97 | content = { PersianText(text = stringResource(R.string.clear_all)) } 98 | ) 99 | } 100 | 101 | @Composable 102 | fun HistoryItem( 103 | history: String, 104 | onClick: (String) -> Unit, 105 | onLongClick: (String) -> Unit 106 | ) { 107 | RemovableCard( 108 | item = history, 109 | onClick = { onClick.invoke(history) }, 110 | onLongClick = { onLongClick(history) } 111 | ) 112 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/yamin8000/owl/content/history/HistoryState.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl: an android app for Owlbot Dictionary API 3 | * HistoryState.kt Created by Yamin Siahmargooei at 2022/8/24 4 | * This file is part of Owl. 5 | * Copyright (C) 2022 Yamin Siahmargooei 6 | * 7 | * Owl is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * Owl is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Owl. If not, see . 19 | */ 20 | 21 | package io.github.yamin8000.owl.content.history 22 | 23 | import android.content.Context 24 | import androidx.compose.runtime.Composable 25 | import androidx.compose.runtime.MutableState 26 | import androidx.compose.runtime.mutableStateOf 27 | import androidx.compose.runtime.remember 28 | import androidx.compose.runtime.saveable.rememberSaveable 29 | import androidx.compose.ui.platform.LocalContext 30 | import androidx.compose.ui.platform.LocalLifecycleOwner 31 | import androidx.datastore.preferences.core.edit 32 | import androidx.datastore.preferences.core.stringPreferencesKey 33 | import androidx.lifecycle.LifecycleCoroutineScope 34 | import androidx.lifecycle.lifecycleScope 35 | import io.github.yamin8000.owl.content.historyDataStore 36 | import io.github.yamin8000.owl.util.list.ListSatiation 37 | import kotlinx.coroutines.launch 38 | 39 | class HistoryState( 40 | private val context: Context, 41 | val lifeCycleScope: LifecycleCoroutineScope, 42 | var history: MutableState> 43 | ) { 44 | 45 | init { 46 | lifeCycleScope.launch { fetchHistory() } 47 | } 48 | 49 | val listSatiation: ListSatiation 50 | get() = when (history.value.size) { 51 | 0 -> ListSatiation.Empty 52 | else -> ListSatiation.Partial 53 | } 54 | 55 | private suspend fun fetchHistory() { 56 | context.historyDataStore.data.collect { preferences -> 57 | val newSet = history.value.toMutableSet() 58 | preferences.asMap().forEach { entry -> newSet.add(entry.value.toString()) } 59 | history.value = newSet 60 | } 61 | } 62 | 63 | suspend fun removeSingleHistory( 64 | singleHistory: String 65 | ) { 66 | context.historyDataStore.edit { it.remove(stringPreferencesKey(singleHistory)) } 67 | val newSet = history.value.toMutableSet() 68 | newSet.remove(singleHistory) 69 | history.value = newSet 70 | } 71 | 72 | suspend fun removeAllHistory() { 73 | context.historyDataStore.edit { it.clear() } 74 | history.value = emptySet() 75 | } 76 | } 77 | 78 | @Composable 79 | fun rememberHistoryState( 80 | context: Context = LocalContext.current, 81 | lifeCycleScope: LifecycleCoroutineScope = LocalLifecycleOwner.current.lifecycleScope, 82 | history: MutableState> = rememberSaveable { mutableStateOf(emptySet()) } 83 | ) = remember(context, lifeCycleScope, history) { HistoryState(context, lifeCycleScope, history) } -------------------------------------------------------------------------------- /app/src/main/java/io/github/yamin8000/owl/content/home/Home.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl: an android app for Owlbot Dictionary API 3 | * Home.kt Created by Yamin Siahmargooei at 2022/8/22 4 | * This file is part of Owl. 5 | * Copyright (C) 2022 Yamin Siahmargooei 6 | * 7 | * Owl is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * Owl is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Owl. If not, see . 19 | */ 20 | 21 | package io.github.yamin8000.owl.content.home 22 | 23 | import android.content.pm.ActivityInfo 24 | import androidx.compose.animation.* 25 | import androidx.compose.foundation.layout.Column 26 | import androidx.compose.foundation.layout.fillMaxSize 27 | import androidx.compose.foundation.layout.fillMaxWidth 28 | import androidx.compose.foundation.layout.padding 29 | import androidx.compose.material3.* 30 | import androidx.compose.runtime.Composable 31 | import androidx.compose.runtime.LaunchedEffect 32 | import androidx.compose.ui.Alignment 33 | import androidx.compose.ui.Modifier 34 | import androidx.compose.ui.hapticfeedback.HapticFeedbackType 35 | import androidx.compose.ui.input.nestedscroll.nestedScroll 36 | import androidx.compose.ui.platform.LocalHapticFeedback 37 | import androidx.compose.ui.res.stringResource 38 | import androidx.compose.ui.text.style.TextAlign 39 | import androidx.compose.ui.unit.dp 40 | import androidx.lifecycle.lifecycleScope 41 | import io.github.yamin8000.owl.R 42 | import io.github.yamin8000.owl.content.MainBottomBar 43 | import io.github.yamin8000.owl.content.MainTopBar 44 | import io.github.yamin8000.owl.ui.composable.* 45 | import kotlinx.coroutines.launch 46 | import java.util.* 47 | 48 | @OptIn(ExperimentalMaterial3Api::class) 49 | @Composable 50 | fun HomeContent( 51 | searchTerm: String?, 52 | onHistoryClick: () -> Unit, 53 | onFavouritesClick: () -> Unit, 54 | onInfoClick: () -> Unit, 55 | onSettingsClick: () -> Unit 56 | ) { 57 | LockScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) 58 | Surface( 59 | modifier = Modifier.fillMaxSize(), 60 | content = { 61 | val state = rememberHomeState() 62 | 63 | if (state.listState.isScrollInProgress && state.isVibrating.value) 64 | LocalHapticFeedback.current.performHapticFeedback(HapticFeedbackType.TextHandleMove) 65 | 66 | InternetAwareComposable { state.isOnline.value = it } 67 | 68 | val locale = if (state.ttsLang.value.isEmpty()) 69 | Locale.US else Locale.forLanguageTag(state.ttsLang.value) 70 | 71 | if (searchTerm != null) { 72 | state.searchText = searchTerm 73 | LaunchedEffect(Unit) { state.addSearchTextToHistory() } 74 | } 75 | LaunchedEffect(state.isOnline.value) { 76 | if (state.isFirstTimeOpening) 77 | state.searchText = "Owl" 78 | if (state.searchText.isNotBlank()) 79 | state.searchForDefinition() 80 | } 81 | 82 | if (state.searchResult.value.item.isNotEmpty() && state.rawWordSearchBody.value != null && state.isSharing.value) 83 | state.handleShareIntent() 84 | 85 | val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() 86 | 87 | Scaffold( 88 | modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), 89 | snackbarHost = { 90 | SnackbarHost(state.snackbarHostState) { data -> 91 | MySnackbar { 92 | PersianText( 93 | text = data.visuals.message, 94 | modifier = Modifier.fillMaxWidth(), 95 | textAlign = TextAlign.Center 96 | ) 97 | } 98 | } 99 | }, 100 | topBar = { 101 | MainTopBar( 102 | scrollBehavior = scrollBehavior, 103 | onHistoryClick = onHistoryClick, 104 | onFavouritesClick = onFavouritesClick, 105 | onInfoClick = onInfoClick, 106 | onSettingsClick = onSettingsClick, 107 | onRandomWordClick = { state.searchForRandomWord() } 108 | ) 109 | }, 110 | bottomBar = { 111 | MainBottomBar( 112 | searchTerm = searchTerm, 113 | suggestions = state.searchSuggestions.value, 114 | isSearching = state.isSearching.value, 115 | onSearchTermChanged = { 116 | state.searchText = it 117 | state.scope.launch { state.handleSuggestions() } 118 | if (state.isWordSelectedFromKeyboardSuggestions) { 119 | state.scope.launch { state.searchForDefinitionHandler() } 120 | state.clearSuggestions() 121 | } 122 | }, 123 | onSuggestionClick = { 124 | state.searchText = it 125 | state.lifecycleOwner.lifecycleScope.launch { state.searchForDefinitionHandler() } 126 | }, 127 | onSearch = { 128 | state.searchText = it 129 | state.lifecycleOwner.lifecycleScope.launch { state.searchForDefinitionHandler() } 130 | }, 131 | onCancel = { state.cancel() } 132 | ) 133 | }, 134 | content = { contentPadding -> 135 | Column( 136 | horizontalAlignment = Alignment.CenterHorizontally, 137 | modifier = Modifier 138 | .padding(contentPadding) 139 | .padding(top = 8.dp), 140 | content = { 141 | AnimatedVisibility( 142 | visible = !state.isOnline.value, 143 | enter = slideInVertically() + fadeIn(), 144 | exit = slideOutVertically() + fadeOut(), 145 | content = { 146 | PersianText( 147 | text = stringResource(R.string.general_net_error), 148 | modifier = Modifier.padding(16.dp), 149 | color = MaterialTheme.colorScheme.error 150 | ) 151 | } 152 | ) 153 | 154 | val addedToFavourites = stringResource(R.string.added_to_favourites) 155 | 156 | if (state.rawWordSearchBody.value != null || state.searchResult.value.item.isNotEmpty()) { 157 | state.rawWordSearchBody.value?.let { word -> 158 | WordCard( 159 | localeTag = locale.toLanguageTag(), 160 | word = word.word, 161 | pronunciation = word.pronunciation, 162 | onShareWord = { state.isSharing.value = true }, 163 | onAddToFavourite = { 164 | state.scope.launch { 165 | state.addToFavourite(word.word) 166 | state.snackbarHostState.showSnackbar( 167 | addedToFavourites 168 | ) 169 | } 170 | } 171 | ) 172 | } 173 | 174 | WordDefinitionsList( 175 | word = state.rawWordSearchBody.value?.word ?: "", 176 | localeTag = locale.toLanguageTag(), 177 | listState = state.listState, 178 | searchResult = state.searchResult.value, 179 | onWordChipClick = { 180 | state.searchText = it 181 | state.lifecycleOwner.lifecycleScope.launch { state.searchForDefinitionHandler() } 182 | } 183 | ) 184 | } else EmptyList() 185 | } 186 | ) 187 | } 188 | ) 189 | } 190 | ) 191 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/yamin8000/owl/content/settings/Settings.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl: an android app for Owlbot Dictionary API 3 | * Settings.kt Created by Yamin Siahmargooei at 2022/9/20 4 | * This file is part of Owl. 5 | * Copyright (C) 2022 Yamin Siahmargooei 6 | * 7 | * Owl is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * Owl is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Owl. If not, see . 19 | */ 20 | 21 | package io.github.yamin8000.owl.content.settings 22 | 23 | import android.os.Build 24 | import android.speech.tts.TextToSpeech 25 | import androidx.compose.foundation.layout.* 26 | import androidx.compose.foundation.lazy.LazyColumn 27 | import androidx.compose.foundation.lazy.items 28 | import androidx.compose.foundation.selection.selectable 29 | import androidx.compose.foundation.selection.selectableGroup 30 | import androidx.compose.material.icons.Icons 31 | import androidx.compose.material.icons.twotone.DisplaySettings 32 | import androidx.compose.material.icons.twotone.Language 33 | import androidx.compose.material3.* 34 | import androidx.compose.runtime.* 35 | import androidx.compose.ui.Alignment 36 | import androidx.compose.ui.Modifier 37 | import androidx.compose.ui.platform.LocalLifecycleOwner 38 | import androidx.compose.ui.res.stringResource 39 | import androidx.compose.ui.semantics.Role 40 | import androidx.compose.ui.text.style.TextAlign 41 | import androidx.compose.ui.unit.dp 42 | import androidx.lifecycle.lifecycleScope 43 | import io.github.yamin8000.owl.R 44 | import io.github.yamin8000.owl.ui.composable.* 45 | import io.github.yamin8000.owl.ui.theme.DefaultCutShape 46 | import io.github.yamin8000.owl.util.speak 47 | import kotlinx.coroutines.launch 48 | import java.util.* 49 | 50 | @OptIn(ExperimentalMaterial3Api::class) 51 | @Composable 52 | fun SettingsContent( 53 | onThemeChanged: (ThemeSetting) -> Unit, 54 | onBackClick: () -> Unit 55 | ) { 56 | val state = rememberSettingsState() 57 | val scope = LocalLifecycleOwner.current.lifecycleScope 58 | 59 | var englishLanguages by remember { mutableStateOf(listOf()) } 60 | var textToSpeech: TextToSpeech? by remember { mutableStateOf(null) } 61 | 62 | TtsAwareFeature( 63 | ttsLanguageLocaleTag = state.ttsLang.value, 64 | onTtsReady = { tts -> 65 | textToSpeech = tts 66 | englishLanguages = tts.availableLanguages.filter { 67 | it.language == Locale.ENGLISH.language 68 | } 69 | } 70 | ) 71 | 72 | ScaffoldWithTitle( 73 | title = stringResource(id = R.string.settings), 74 | onBackClick = onBackClick 75 | ) { 76 | Column( 77 | horizontalAlignment = Alignment.CenterHorizontally, 78 | verticalArrangement = Arrangement.spacedBy(8.dp) 79 | ) { 80 | GeneralSettings( 81 | isVibrating = state.isVibrating.value, 82 | isVibratingChange = { state.scope.launch { state.updateVibrationSetting(it) } } 83 | ) 84 | ThemeSetting(state.themeSetting.value) { newTheme -> 85 | state.scope.launch { state.updateThemeSetting(newTheme) } 86 | onThemeChanged(newTheme) 87 | } 88 | TtsLanguageSetting( 89 | languages = englishLanguages, 90 | currentTtsTag = state.ttsLang.value, 91 | onTtsTagChanged = { tag -> 92 | scope.launch { 93 | state.updateTtsLang(tag) 94 | textToSpeech?.speak(Locale.forLanguageTag(tag).displayName) 95 | } 96 | } 97 | ) 98 | } 99 | } 100 | } 101 | 102 | @Composable 103 | fun TtsLanguageSetting( 104 | currentTtsTag: String, 105 | languages: List, 106 | onTtsTagChanged: (String) -> Unit 107 | ) { 108 | var isDialogShown by remember { mutableStateOf(false) } 109 | 110 | if (isDialogShown) { 111 | TtsLanguagesDialog( 112 | currentTtsTag = currentTtsTag, 113 | languages = languages, 114 | onLanguageSelected = onTtsTagChanged, 115 | onDismiss = { isDialogShown = false } 116 | ) 117 | } 118 | 119 | SettingsItemCard( 120 | title = stringResource(R.string.tts_language), 121 | content = { 122 | SettingsItem( 123 | onClick = { isDialogShown = true }, 124 | content = { 125 | Icon(imageVector = Icons.TwoTone.Language, contentDescription = null) 126 | PersianText(Locale.forLanguageTag(currentTtsTag).displayName) 127 | } 128 | ) 129 | } 130 | ) 131 | } 132 | 133 | @Composable 134 | fun TtsLanguagesDialog( 135 | currentTtsTag: String, 136 | languages: List, 137 | onLanguageSelected: (String) -> Unit, 138 | onDismiss: () -> Unit 139 | ) { 140 | AlertDialog( 141 | onDismissRequest = { onDismiss() }, 142 | title = { PersianText(stringResource(R.string.tts_language)) }, 143 | icon = { Icon(imageVector = Icons.TwoTone.Language, contentDescription = null) }, 144 | confirmButton = {/*ignored*/ }, 145 | text = { 146 | LazyColumn( 147 | horizontalAlignment = Alignment.CenterHorizontally, 148 | verticalArrangement = Arrangement.spacedBy(8.dp), 149 | content = { 150 | items(languages) { 151 | TtsLanguageItem( 152 | localeTag = it.toLanguageTag(), 153 | isSelected = it.toLanguageTag() == currentTtsTag 154 | ) { tag -> 155 | onLanguageSelected(tag) 156 | onDismiss() 157 | } 158 | } 159 | } 160 | ) 161 | } 162 | ) 163 | } 164 | 165 | @Composable 166 | fun GeneralSettings( 167 | isVibrating: Boolean, 168 | isVibratingChange: (Boolean) -> Unit 169 | ) { 170 | SettingsItemCard( 171 | modifier = Modifier.fillMaxWidth(), 172 | title = stringResource(R.string.general), 173 | content = { 174 | Row( 175 | horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.Start), 176 | verticalAlignment = Alignment.CenterVertically 177 | ) { 178 | Icon(imageVector = Icons.TwoTone.Language, contentDescription = null) 179 | SwitchWithText( 180 | caption = stringResource(R.string.vibrate_on_scroll), 181 | checked = isVibrating, 182 | onCheckedChange = isVibratingChange 183 | ) 184 | } 185 | } 186 | ) 187 | } 188 | 189 | @OptIn(ExperimentalMaterial3Api::class) 190 | @Composable 191 | fun TtsLanguageItem( 192 | localeTag: String, 193 | isSelected: Boolean, 194 | onClick: ((String) -> Unit)? = null 195 | ) { 196 | OutlinedCard( 197 | modifier = Modifier.fillMaxWidth(), 198 | shape = DefaultCutShape, 199 | onClick = { onClick?.invoke(localeTag) }, 200 | enabled = !isSelected 201 | ) { 202 | PersianText( 203 | text = Locale.forLanguageTag(localeTag).displayName, 204 | modifier = Modifier.padding(16.dp) 205 | ) 206 | } 207 | } 208 | 209 | @Composable 210 | fun ThemeSetting( 211 | currentTheme: ThemeSetting, 212 | onCurrentThemeChange: (ThemeSetting) -> Unit 213 | ) { 214 | var isShowingThemeDialog by remember { mutableStateOf(false) } 215 | 216 | SettingsItemCard( 217 | title = stringResource(R.string.theme), 218 | content = { 219 | if (isShowingThemeDialog) { 220 | ThemeChangerDialog( 221 | currentTheme = currentTheme, 222 | onCurrentThemeChange = onCurrentThemeChange, 223 | onDismiss = { isShowingThemeDialog = false } 224 | ) 225 | } 226 | SettingsItem( 227 | onClick = { isShowingThemeDialog = true }, 228 | content = { 229 | Icon( 230 | imageVector = Icons.TwoTone.DisplaySettings, 231 | contentDescription = stringResource(R.string.theme) 232 | ) 233 | PersianText( 234 | text = stringResource(currentTheme.persianNameStringResource), 235 | modifier = Modifier.padding() 236 | ) 237 | } 238 | ) 239 | if (currentTheme == ThemeSetting.System && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) 240 | DynamicThemeNotice() 241 | } 242 | ) 243 | } 244 | 245 | @Composable 246 | fun ThemeChangerDialog( 247 | currentTheme: ThemeSetting, 248 | onCurrentThemeChange: (ThemeSetting) -> Unit, 249 | onDismiss: () -> Unit 250 | ) { 251 | val themes = remember { ThemeSetting.values() } 252 | AlertDialog( 253 | onDismissRequest = onDismiss, 254 | confirmButton = { /*ignored*/ }, 255 | title = { PersianText(stringResource(R.string.theme)) }, 256 | icon = { Icon(imageVector = Icons.TwoTone.DisplaySettings, contentDescription = null) }, 257 | text = { 258 | Column( 259 | horizontalAlignment = Alignment.CenterHorizontally, 260 | verticalArrangement = Arrangement.SpaceBetween, 261 | modifier = Modifier 262 | .padding(16.dp) 263 | .selectableGroup() 264 | .fillMaxWidth(), 265 | content = { 266 | themes.forEach { theme -> 267 | Row( 268 | verticalAlignment = Alignment.CenterVertically, 269 | horizontalArrangement = Arrangement.spacedBy(2.dp, Alignment.Start), 270 | modifier = Modifier 271 | .fillMaxWidth() 272 | .selectable( 273 | selected = (theme == currentTheme), 274 | role = Role.RadioButton, 275 | onClick = { 276 | onCurrentThemeChange(theme) 277 | onDismiss() 278 | } 279 | ), 280 | content = { 281 | RadioButton( 282 | selected = (theme == currentTheme), 283 | onClick = null, 284 | modifier = Modifier.padding(start = 8.dp) 285 | ) 286 | PersianText( 287 | text = stringResource(theme.persianNameStringResource), 288 | modifier = Modifier.padding(vertical = 16.dp) 289 | ) 290 | } 291 | ) 292 | } 293 | } 294 | ) 295 | } 296 | ) 297 | } 298 | 299 | @Composable 300 | fun DynamicThemeNotice() { 301 | PersianText( 302 | text = stringResource(R.string.dynamic_theme_notice), 303 | textAlign = TextAlign.Justify 304 | ) 305 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/yamin8000/owl/content/settings/SettingsState.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl: an android app for Owlbot Dictionary API 3 | * SettingsState.kt Created by Yamin Siahmargooei at 2022/9/20 4 | * This file is part of Owl. 5 | * Copyright (C) 2022 Yamin Siahmargooei 6 | * 7 | * Owl is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * Owl is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Owl. If not, see . 19 | */ 20 | 21 | package io.github.yamin8000.owl.content.settings 22 | 23 | import android.content.Context 24 | import androidx.compose.runtime.Composable 25 | import androidx.compose.runtime.MutableState 26 | import androidx.compose.runtime.mutableStateOf 27 | import androidx.compose.runtime.remember 28 | import androidx.compose.runtime.saveable.rememberSaveable 29 | import androidx.compose.ui.platform.LocalContext 30 | import androidx.compose.ui.platform.LocalLifecycleOwner 31 | import androidx.lifecycle.LifecycleCoroutineScope 32 | import androidx.lifecycle.lifecycleScope 33 | import io.github.yamin8000.owl.content.settingsDataStore 34 | import io.github.yamin8000.owl.util.Constants 35 | import io.github.yamin8000.owl.util.DataStoreHelper 36 | import kotlinx.coroutines.launch 37 | import java.util.Locale 38 | 39 | class SettingsState( 40 | context: Context, 41 | val scope: LifecycleCoroutineScope, 42 | val themeSetting: MutableState, 43 | var ttsLang: MutableState, 44 | val isVibrating: MutableState 45 | ) { 46 | private val dataStore = DataStoreHelper(context.settingsDataStore) 47 | 48 | init { 49 | scope.launch { 50 | themeSetting.value = ThemeSetting.valueOf( 51 | dataStore.getString(Constants.THEME) ?: ThemeSetting.System.name 52 | ) 53 | ttsLang.value = dataStore.getString(Constants.TTS_LANG) ?: Locale.US.toLanguageTag() 54 | isVibrating.value = dataStore.getBool(Constants.IS_VIBRATING) ?: true 55 | } 56 | } 57 | 58 | suspend fun updateTtsLang( 59 | newTtsLang: String 60 | ) { 61 | ttsLang.value = newTtsLang 62 | dataStore.setString(Constants.TTS_LANG, newTtsLang) 63 | } 64 | 65 | suspend fun updateThemeSetting( 66 | newTheme: ThemeSetting 67 | ) { 68 | themeSetting.value = newTheme 69 | dataStore.setString(Constants.THEME, newTheme.name) 70 | } 71 | 72 | suspend fun updateVibrationSetting( 73 | newVibrationSetting: Boolean 74 | ) { 75 | isVibrating.value = newVibrationSetting 76 | dataStore.setBool(Constants.IS_VIBRATING, newVibrationSetting) 77 | } 78 | } 79 | 80 | @Composable 81 | fun rememberSettingsState( 82 | context: Context = LocalContext.current, 83 | coroutineScope: LifecycleCoroutineScope = LocalLifecycleOwner.current.lifecycleScope, 84 | themeSetting: MutableState = rememberSaveable { mutableStateOf(ThemeSetting.System) }, 85 | ttsLang: MutableState = rememberSaveable { mutableStateOf(Locale.US.toLanguageTag()) }, 86 | isVibrating: MutableState = rememberSaveable { mutableStateOf(true) } 87 | ) = remember(context, themeSetting, coroutineScope, ttsLang, isVibrating) { 88 | SettingsState(context, coroutineScope, themeSetting, ttsLang, isVibrating) 89 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/yamin8000/owl/content/settings/ThemeSetting.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl: an android app for Owlbot Dictionary API 3 | * ThemeSetting.kt Created by Yamin Siahmargooei at 2022/9/20 4 | * This file is part of Owl. 5 | * Copyright (C) 2022 Yamin Siahmargooei 6 | * 7 | * Owl is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * Owl is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Owl. If not, see . 19 | */ 20 | 21 | package io.github.yamin8000.owl.content.settings 22 | 23 | import android.os.Parcelable 24 | import androidx.annotation.StringRes 25 | import io.github.yamin8000.owl.R 26 | import kotlinx.parcelize.Parcelize 27 | 28 | @Parcelize 29 | enum class ThemeSetting( 30 | @StringRes val persianNameStringResource: Int 31 | ) : Parcelable { 32 | Dark(R.string.theme_dark), Light(R.string.theme_light), System(R.string.theme_system); 33 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/yamin8000/owl/db/AppDatabase.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl/Owl.app.main 3 | * AppDatabase.kt Copyrighted by Yamin Siahmargooei at 2022/12/27 4 | * AppDatabase.kt Last modified at 2022/12/27 5 | * This file is part of Owl/Owl.app.main. 6 | * Copyright (C) 2022 Yamin Siahmargooei 7 | * 8 | * Owl/Owl.app.main is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * Owl/Owl.app.main is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with Owl. If not, see . 20 | */ 21 | 22 | package io.github.yamin8000.owl.db 23 | 24 | import androidx.room.Database 25 | import androidx.room.RoomDatabase 26 | import io.github.yamin8000.owl.db.dao.DAOs 27 | import io.github.yamin8000.owl.db.entity.DefinitionEntity 28 | import io.github.yamin8000.owl.db.entity.WordEntity 29 | 30 | @Database( 31 | entities = [WordEntity::class, DefinitionEntity::class], 32 | version = 1 33 | ) 34 | abstract class AppDatabase : RoomDatabase() { 35 | abstract fun wordDao(): DAOs.WordDao 36 | abstract fun definitionDao(): DAOs.DefinitionDao 37 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/yamin8000/owl/db/dao/BaseDao.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl/Owl.app.main 3 | * BaseDao.kt Copyrighted by Yamin Siahmargooei at 2022/12/27 4 | * BaseDao.kt Last modified at 2022/12/27 5 | * This file is part of Owl/Owl.app.main. 6 | * Copyright (C) 2022 Yamin Siahmargooei 7 | * 8 | * Owl/Owl.app.main is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * Owl/Owl.app.main is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with Owl. If not, see . 20 | */ 21 | 22 | package io.github.yamin8000.owl.db.dao 23 | 24 | import androidx.room.Delete 25 | import androidx.room.Insert 26 | import androidx.room.RawQuery 27 | import androidx.room.Update 28 | import androidx.sqlite.db.SimpleSQLiteQuery 29 | import androidx.sqlite.db.SupportSQLiteQuery 30 | 31 | abstract class BaseDao(tableName: String) { 32 | 33 | private val baseQuery = "select * from `$tableName`" 34 | 35 | private val baseWhereQuery = "$baseQuery where" 36 | 37 | @Insert 38 | abstract suspend fun insert(entity: T): Long 39 | 40 | @Insert 41 | abstract suspend fun insertAll(entities: List): List 42 | 43 | @Update 44 | abstract suspend fun update(entity: T): Int 45 | 46 | @Delete 47 | abstract suspend fun delete(entity: T): Int 48 | 49 | @Delete 50 | abstract suspend fun deleteAll(entities: List): Int 51 | 52 | @RawQuery 53 | protected abstract suspend fun getById(query: SupportSQLiteQuery): T? 54 | 55 | suspend fun getById(id: Long) = getById(SimpleSQLiteQuery("$baseQuery where id = $id")) 56 | 57 | @RawQuery 58 | protected abstract suspend fun getAll(query: SupportSQLiteQuery): List 59 | 60 | suspend fun getAll() = getAll(SimpleSQLiteQuery(baseQuery)) 61 | 62 | @RawQuery 63 | protected abstract suspend fun getAllByIds(query: SupportSQLiteQuery): List 64 | 65 | suspend fun getAllByIds( 66 | ids: List 67 | ): List { 68 | val params = ids.joinToString("") 69 | return getAllByIds(SimpleSQLiteQuery("$baseWhereQuery id in ($params)")) 70 | } 71 | 72 | @RawQuery 73 | protected abstract suspend fun getByParam(query: SupportSQLiteQuery): List 74 | 75 | suspend fun

getByParam( 76 | param: String, value: P 77 | ) = getByParam(SimpleSQLiteQuery("$baseWhereQuery $param='$value'")) 78 | 79 | suspend fun getByParams( 80 | paramPair: Pair, 81 | vararg paramPairs: Pair 82 | ): List { 83 | val params = listOf(paramPair, *paramPairs) 84 | val condition = buildString { 85 | params.forEachIndexed { index, pair -> 86 | append("${pair.first}='${pair.second}'") 87 | if (index != params.lastIndex) append(" and ") 88 | } 89 | } 90 | return getByParam(SimpleSQLiteQuery("$baseWhereQuery $condition")) 91 | } 92 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/yamin8000/owl/db/dao/DAOs.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl/Owl.app.main 3 | * DAOs.kt Copyrighted by Yamin Siahmargooei at 2022/12/27 4 | * DAOs.kt Last modified at 2022/12/27 5 | * This file is part of Owl/Owl.app.main. 6 | * Copyright (C) 2022 Yamin Siahmargooei 7 | * 8 | * Owl/Owl.app.main is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * Owl/Owl.app.main is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with Owl. If not, see . 20 | */ 21 | 22 | package io.github.yamin8000.owl.db.dao 23 | 24 | import androidx.room.Dao 25 | import io.github.yamin8000.owl.db.entity.DefinitionEntity 26 | import io.github.yamin8000.owl.db.entity.WordEntity 27 | 28 | object DAOs { 29 | @Dao 30 | abstract class DefinitionDao : BaseDao("DefinitionEntity") 31 | 32 | @Dao 33 | abstract class WordDao : BaseDao("WordEntity") 34 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/yamin8000/owl/db/entity/DefinitionEntity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl/Owl.app.main 3 | * DefinitionEntity.kt Copyrighted by Yamin Siahmargooei at 2022/12/27 4 | * DefinitionEntity.kt Last modified at 2022/12/27 5 | * This file is part of Owl/Owl.app.main. 6 | * Copyright (C) 2022 Yamin Siahmargooei 7 | * 8 | * Owl/Owl.app.main is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * Owl/Owl.app.main is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with Owl. If not, see . 20 | */ 21 | 22 | package io.github.yamin8000.owl.db.entity 23 | 24 | import androidx.room.ColumnInfo 25 | import androidx.room.Entity 26 | import androidx.room.ForeignKey 27 | import androidx.room.PrimaryKey 28 | 29 | @Entity( 30 | foreignKeys = [ 31 | ForeignKey( 32 | entity = WordEntity::class, 33 | parentColumns = ["id"], 34 | childColumns = ["wordId"], 35 | onDelete = ForeignKey.RESTRICT 36 | ) 37 | ] 38 | ) 39 | data class DefinitionEntity( 40 | @ColumnInfo(name = "wordId", index = true) 41 | val wordId: Long, 42 | val type: String?, 43 | val definition: String, 44 | val example: String?, 45 | val imageUrl: String?, 46 | val emoji: String?, 47 | @PrimaryKey(autoGenerate = true) 48 | val id: Long = 0 49 | ) 50 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/yamin8000/owl/db/entity/WordEntity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl/Owl.app.main 3 | * WordEntity.kt Copyrighted by Yamin Siahmargooei at 2022/12/27 4 | * WordEntity.kt Last modified at 2022/12/27 5 | * This file is part of Owl/Owl.app.main. 6 | * Copyright (C) 2022 Yamin Siahmargooei 7 | * 8 | * Owl/Owl.app.main is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * Owl/Owl.app.main is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with Owl. If not, see . 20 | */ 21 | 22 | package io.github.yamin8000.owl.db.entity 23 | 24 | import androidx.room.Entity 25 | import androidx.room.PrimaryKey 26 | 27 | @Entity 28 | data class WordEntity( 29 | val word: String, 30 | val pronunciation: String? = null, 31 | @PrimaryKey(autoGenerate = true) 32 | val id: Long = 0 33 | ) 34 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/yamin8000/owl/model/Definition.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl: an android app for Owlbot Dictionary API 3 | * Definition.kt Created by Yamin Siahmargooei at 2022/1/16 4 | * This file is part of Owl. 5 | * Copyright (C) 2022 Yamin Siahmargooei 6 | * 7 | * Owl is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * Owl is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Owl. If not, see . 19 | */ 20 | 21 | package io.github.yamin8000.owl.model 22 | 23 | import android.os.Parcelable 24 | import androidx.compose.runtime.Immutable 25 | import com.squareup.moshi.Json 26 | import io.github.yamin8000.owl.util.Constants.IMAGE_URL 27 | import kotlinx.parcelize.Parcelize 28 | 29 | @Immutable 30 | @Parcelize 31 | data class Definition( 32 | val type: String?, 33 | val definition: String, 34 | val example: String?, 35 | @field:Json(name = IMAGE_URL) val imageUrl: String?, 36 | val emoji: String? 37 | ) : Parcelable 38 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/yamin8000/owl/model/RandomWord.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl: an android app for Owlbot Dictionary API 3 | * RandomWord.kt Created by Yamin Siahmargooei at 2022/8/20 4 | * This file is part of Owl. 5 | * Copyright (C) 2022 Yamin Siahmargooei 6 | * 7 | * Owl is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * Owl is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Owl. If not, see . 19 | */ 20 | 21 | package io.github.yamin8000.owl.model 22 | 23 | data class RandomWord(val word: String) -------------------------------------------------------------------------------- /app/src/main/java/io/github/yamin8000/owl/model/Word.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl: an android app for Owlbot Dictionary API 3 | * Word.kt Created by Yamin Siahmargooei at 2022/1/16 4 | * This file is part of Owl. 5 | * Copyright (C) 2022 Yamin Siahmargooei 6 | * 7 | * Owl is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * Owl is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Owl. If not, see . 19 | */ 20 | 21 | package io.github.yamin8000.owl.model 22 | 23 | import android.os.Parcelable 24 | import kotlinx.parcelize.Parcelize 25 | 26 | @Parcelize 27 | data class Word( 28 | val word: String, 29 | val pronunciation: String?, 30 | val definitions: List 31 | ) : Parcelable 32 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/yamin8000/owl/network/APIs.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl: an android app for Owlbot Dictionary API 3 | * APIs.kt Created by Yamin Siahmargooei at 2022/1/16 4 | * This file is part of Owl. 5 | * Copyright (C) 2022 Yamin Siahmargooei 6 | * 7 | * Owl is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * Owl is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Owl. If not, see . 19 | */ 20 | 21 | package io.github.yamin8000.owl.network 22 | 23 | import io.github.yamin8000.owl.BuildConfig 24 | import io.github.yamin8000.owl.model.RandomWord 25 | import io.github.yamin8000.owl.model.Word 26 | import retrofit2.http.GET 27 | import retrofit2.http.Headers 28 | import retrofit2.http.Path 29 | 30 | object APIs { 31 | 32 | interface OwlBotWordAPI { 33 | 34 | @Headers(BuildConfig.OWLBOT_TOKEN) 35 | @GET("dictionary/{word}") 36 | suspend fun searchWord(@Path("word") word: String): Word? 37 | } 38 | 39 | interface NinjaAPI { 40 | 41 | @Suppress("SpellCheckingInspection") 42 | @Headers(BuildConfig.API_NINJA_KEY) 43 | @GET("randomword") 44 | suspend fun getRandomWord(): RandomWord 45 | } 46 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/yamin8000/owl/network/Web.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl: an android app for Owlbot Dictionary API 3 | * Web.kt Created by Yamin Siahmargooei at 2022/1/16 4 | * This file is part of Owl. 5 | * Copyright (C) 2022 Yamin Siahmargooei 6 | * 7 | * Owl is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * Owl is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Owl. If not, see . 19 | */ 20 | 21 | package io.github.yamin8000.owl.network 22 | 23 | import retrofit2.Retrofit 24 | import retrofit2.converter.moshi.MoshiConverterFactory 25 | 26 | @Suppress("SameParameterValue") 27 | object Web { 28 | 29 | private const val baseUrl = "https://owlbot.info/api/v4/" 30 | private const val ninjaApiBaseUrl = "https://api.api-ninjas.com/v1/" 31 | 32 | val retrofit: Retrofit by lazy(LazyThreadSafetyMode.NONE) { createRetrofit() } 33 | 34 | val ninjaApiRetrofit: Retrofit by lazy(LazyThreadSafetyMode.NONE) { createNinjaApiRetrofit() } 35 | 36 | private fun createNinjaApiRetrofit(): Retrofit { 37 | return createCustomUrlRetrofit(ninjaApiBaseUrl) 38 | } 39 | 40 | private fun createCustomUrlRetrofit(baseUrl: String): Retrofit { 41 | return Retrofit.Builder() 42 | .baseUrl(baseUrl) 43 | .addConverterFactory(MoshiConverterFactory.create()) 44 | .build() 45 | } 46 | 47 | private fun createRetrofit(): Retrofit { 48 | return Retrofit.Builder() 49 | .baseUrl(baseUrl) 50 | .addConverterFactory(MoshiConverterFactory.create()) 51 | .build() 52 | } 53 | 54 | /** 55 | * get service of given api/interface 56 | * 57 | * @param T type/class/interface of api 58 | * @return service for that api 59 | */ 60 | inline fun Retrofit.getAPI(): T = this.create(T::class.java) 61 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/yamin8000/owl/ui/composable/Animations.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl: an android app for Owlbot Dictionary API 3 | * Animations.kt Created by Yamin Siahmargooei at 2022/11/8 4 | * This file is part of Owl. 5 | * Copyright (C) 2022 Yamin Siahmargooei 6 | * 7 | * Owl is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * Owl is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Owl. If not, see . 19 | */ 20 | 21 | package io.github.yamin8000.owl.ui.composable 22 | 23 | import androidx.compose.animation.core.RepeatMode 24 | import androidx.compose.animation.core.VectorConverter 25 | import androidx.compose.animation.core.animate 26 | import androidx.compose.animation.core.infiniteRepeatable 27 | import androidx.compose.animation.core.tween 28 | import androidx.compose.foundation.layout.size 29 | import androidx.compose.material3.Icon 30 | import androidx.compose.material3.MaterialTheme 31 | import androidx.compose.runtime.Composable 32 | import androidx.compose.runtime.LaunchedEffect 33 | import androidx.compose.runtime.getValue 34 | import androidx.compose.runtime.mutableStateOf 35 | import androidx.compose.runtime.remember 36 | import androidx.compose.runtime.setValue 37 | import androidx.compose.ui.Modifier 38 | import androidx.compose.ui.res.painterResource 39 | import androidx.compose.ui.res.stringResource 40 | import androidx.compose.ui.unit.Dp 41 | import androidx.compose.ui.unit.dp 42 | import io.github.yamin8000.owl.R 43 | 44 | @Composable 45 | fun AnimatedAppIcon( 46 | isAnimating: Boolean = true 47 | ) { 48 | var size by remember { mutableStateOf(0.dp) } 49 | 50 | LaunchedEffect(Unit) { 51 | while (isAnimating) { 52 | animate( 53 | typeConverter = Dp.VectorConverter, 54 | initialValue = 64.dp, 55 | targetValue = 56.dp, 56 | animationSpec = infiniteRepeatable( 57 | animation = tween(1000), 58 | repeatMode = RepeatMode.Reverse 59 | ), 60 | block = { value, _ -> size = value } 61 | ) 62 | } 63 | } 64 | 65 | Icon( 66 | painter = painterResource(R.drawable.ic_launcher_foreground), 67 | contentDescription = stringResource(R.string.app_name), 68 | tint = MaterialTheme.colorScheme.onSurface, 69 | modifier = Modifier.size(size) 70 | ) 71 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/yamin8000/owl/ui/composable/Cards.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl: an android app for Owlbot Dictionary API 3 | * Cards.kt Created by Yamin Siahmargooei at 2022/8/24 4 | * This file is part of Owl. 5 | * Copyright (C) 2022 Yamin Siahmargooei 6 | * 7 | * Owl is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * Owl is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Owl. If not, see . 19 | */ 20 | 21 | package io.github.yamin8000.owl.ui.composable 22 | 23 | import androidx.compose.foundation.layout.Arrangement 24 | import androidx.compose.foundation.layout.Column 25 | import androidx.compose.foundation.layout.ColumnScope 26 | import androidx.compose.foundation.layout.fillMaxWidth 27 | import androidx.compose.foundation.layout.padding 28 | import androidx.compose.material3.Card 29 | import androidx.compose.material3.MaterialTheme 30 | import androidx.compose.material3.Text 31 | import androidx.compose.runtime.Composable 32 | import androidx.compose.runtime.getValue 33 | import androidx.compose.runtime.mutableStateOf 34 | import androidx.compose.runtime.remember 35 | import androidx.compose.runtime.setValue 36 | import androidx.compose.ui.Alignment 37 | import androidx.compose.ui.Modifier 38 | import androidx.compose.ui.hapticfeedback.HapticFeedbackType 39 | import androidx.compose.ui.platform.LocalHapticFeedback 40 | import androidx.compose.ui.text.style.TextAlign 41 | import androidx.compose.ui.unit.dp 42 | import androidx.compose.ui.unit.sp 43 | import io.github.yamin8000.owl.ui.theme.DefaultCutShape 44 | 45 | @Composable 46 | fun SettingsItemCard( 47 | modifier: Modifier = Modifier, 48 | columnModifier: Modifier = Modifier, 49 | title: String, 50 | content: @Composable ColumnScope.() -> Unit 51 | ) { 52 | Column( 53 | verticalArrangement = Arrangement.spacedBy(4.dp), 54 | horizontalAlignment = Alignment.Start, 55 | content = { 56 | PersianText( 57 | text = title, 58 | fontSize = 18.sp, 59 | color = MaterialTheme.colorScheme.primary 60 | ) 61 | Card( 62 | modifier = modifier, 63 | shape = DefaultCutShape, 64 | content = { 65 | Column( 66 | modifier = columnModifier.padding(16.dp), 67 | verticalArrangement = Arrangement.spacedBy(8.dp), 68 | horizontalAlignment = Alignment.CenterHorizontally, 69 | content = { content() } 70 | ) 71 | } 72 | ) 73 | }, 74 | ) 75 | } 76 | 77 | @Composable 78 | fun RemovableCard( 79 | item: String, 80 | onClick: () -> Unit, 81 | onLongClick: () -> Unit 82 | ) { 83 | var isMenuExpanded by remember { mutableStateOf(false) } 84 | val haptic = LocalHapticFeedback.current 85 | Card( 86 | shape = DefaultCutShape, 87 | content = { 88 | Ripple( 89 | modifier = Modifier.padding(8.dp), 90 | onClick = onClick, 91 | onLongClick = { isMenuExpanded = true }, 92 | content = { 93 | Text( 94 | text = item, 95 | textAlign = TextAlign.Center, 96 | modifier = Modifier 97 | .padding(16.dp) 98 | .fillMaxWidth() 99 | ) 100 | } 101 | ) 102 | DeleteMenu( 103 | expanded = isMenuExpanded, 104 | onDismiss = { isMenuExpanded = false }, 105 | onDelete = { 106 | isMenuExpanded = false 107 | haptic.performHapticFeedback(HapticFeedbackType.LongPress) 108 | onLongClick() 109 | } 110 | ) 111 | } 112 | ) 113 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/yamin8000/owl/ui/navigation/Nav.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl: an android app for Owlbot Dictionary API 3 | * NavigationConstants.kt Created by Yamin Siahmargooei at 2022/8/21 4 | * This file is part of Owl. 5 | * Copyright (C) 2022 Yamin Siahmargooei 6 | * 7 | * Owl is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * Owl is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Owl. If not, see . 19 | */ 20 | 21 | package io.github.yamin8000.owl.ui.navigation 22 | 23 | object Nav { 24 | object Routes { 25 | data object Home 26 | data object History 27 | data object Settings 28 | data object About 29 | data object Favourites 30 | } 31 | 32 | object Arguments { 33 | data object Search 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/yamin8000/owl/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl: an android app for Owlbot Dictionary API 3 | * Color.kt Created by Yamin Siahmargooei at 2022/6/16 4 | * This file is part of Owl. 5 | * Copyright (C) 2022 Yamin Siahmargooei 6 | * 7 | * Owl is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * Owl is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Owl. If not, see . 19 | */ 20 | 21 | package io.github.yamin8000.owl.ui.theme 22 | 23 | import androidx.compose.ui.graphics.Color 24 | 25 | val md_theme_light_primary = Color(0xFFBB0055) 26 | val md_theme_light_onPrimary = Color(0xFFFFFFFF) 27 | val md_theme_light_primaryContainer = Color(0xFFFFC4CE) 28 | val md_theme_light_onPrimaryContainer = Color(0xFF3F0018) 29 | val md_theme_light_secondary = Color(0xFF75565C) 30 | val md_theme_light_onSecondary = Color(0xFFFFFFFF) 31 | val md_theme_light_secondaryContainer = Color(0xFFFFD9DF) 32 | val md_theme_light_onSecondaryContainer = Color(0xFF2B151A) 33 | val md_theme_light_tertiary = Color(0xFF7A5733) 34 | val md_theme_light_onTertiary = Color(0xFFFFFFFF) 35 | val md_theme_light_tertiaryContainer = Color(0xFFFFDCBD) 36 | val md_theme_light_onTertiaryContainer = Color(0xFF2C1600) 37 | val md_theme_light_error = Color(0xFFBA1A1A) 38 | val md_theme_light_errorContainer = Color(0xFFFFDAD6) 39 | val md_theme_light_onError = Color(0xFFFFFFFF) 40 | val md_theme_light_onErrorContainer = Color(0xFF410002) 41 | val md_theme_light_background = Color(0xFFFBE9E7) 42 | val md_theme_light_onBackground = Color(0xFF201A1B) 43 | val md_theme_light_surface = Color(0xFFFBE9E7) 44 | val md_theme_light_onSurface = Color(0xFF201A1B) 45 | val md_theme_light_surfaceVariant = Color(0xFFF3DDE0) 46 | val md_theme_light_onSurfaceVariant = Color(0xFF524346) 47 | val md_theme_light_outline = Color(0xFF847375) 48 | val md_theme_light_inverseOnSurface = Color(0xFFFAEEEF) 49 | val md_theme_light_inverseSurface = Color(0xFF352F30) 50 | val md_theme_light_inversePrimary = Color(0xFFFFB1C2) 51 | //val md_theme_light_shadow = Color(0xFF000000) 52 | val md_theme_light_surfaceTint = Color(0xFFBB0055) 53 | //val md_theme_light_surfaceTintColor = Color(0xFFBB0055) 54 | 55 | val md_theme_dark_primary = Color(0xFFFFB1C2) 56 | val md_theme_dark_onPrimary = Color(0xFF66002B) 57 | val md_theme_dark_primaryContainer = Color(0xFF8F0040) 58 | val md_theme_dark_onPrimaryContainer = Color(0xFFFFD9DF) 59 | val md_theme_dark_secondary = Color(0xFFE4BDC4) 60 | val md_theme_dark_onSecondary = Color(0xFF43292F) 61 | val md_theme_dark_secondaryContainer = Color(0xFF5B3F45) 62 | val md_theme_dark_onSecondaryContainer = Color(0xFFFFD9DF) 63 | val md_theme_dark_tertiary = Color(0xFFECBE91) 64 | val md_theme_dark_onTertiary = Color(0xFF462A09) 65 | val md_theme_dark_tertiaryContainer = Color(0xFF60401E) 66 | val md_theme_dark_onTertiaryContainer = Color(0xFFFFDCBD) 67 | val md_theme_dark_error = Color(0xFFFFB4AB) 68 | val md_theme_dark_errorContainer = Color(0xFF93000A) 69 | val md_theme_dark_onError = Color(0xFF690005) 70 | val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6) 71 | val md_theme_dark_background = Color(0xFF201A1B) 72 | val md_theme_dark_onBackground = Color(0xFFECE0E0) 73 | val md_theme_dark_surface = Color(0xFF201A1B) 74 | val md_theme_dark_onSurface = Color(0xFFECE0E0) 75 | val md_theme_dark_surfaceVariant = Color(0xFF524346) 76 | val md_theme_dark_onSurfaceVariant = Color(0xFFD6C2C4) 77 | val md_theme_dark_outline = Color(0xFF9E8C8F) 78 | val md_theme_dark_inverseOnSurface = Color(0xFF201A1B) 79 | val md_theme_dark_inverseSurface = Color(0xFFECE0E0) 80 | val md_theme_dark_inversePrimary = Color(0xFFBB0055) 81 | //val md_theme_dark_shadow = Color(0xFF000000) 82 | val md_theme_dark_surfaceTint = Color(0xFFFFB1C2) 83 | //val md_theme_dark_surfaceTintColor = Color(0xFFFFB1C2) 84 | 85 | 86 | //val seed = Color(0xFFBB0055) -------------------------------------------------------------------------------- /app/src/main/java/io/github/yamin8000/owl/ui/theme/Shape.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl/Owl.app.main 3 | * Shape.kt Copyrighted by Yamin Siahmargooei at 2023/1/7 4 | * Shape.kt Last modified at 2023/1/7 5 | * This file is part of Owl/Owl.app.main. 6 | * Copyright (C) 2023 Yamin Siahmargooei 7 | * 8 | * Owl/Owl.app.main is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * Owl/Owl.app.main is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with Owl. If not, see . 20 | */ 21 | 22 | package io.github.yamin8000.owl.ui.theme 23 | 24 | import androidx.compose.foundation.shape.CutCornerShape 25 | import androidx.compose.ui.unit.dp 26 | 27 | val DefaultCutShape = CutCornerShape(15.dp) -------------------------------------------------------------------------------- /app/src/main/java/io/github/yamin8000/owl/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl: an android app for Owlbot Dictionary API 3 | * Theme.kt Created by Yamin Siahmargooei at 2022/6/16 4 | * This file is part of Owl. 5 | * Copyright (C) 2022 Yamin Siahmargooei 6 | * 7 | * Owl is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * Owl is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Owl. If not, see . 19 | */ 20 | 21 | package io.github.yamin8000.owl.ui.theme 22 | 23 | import android.app.Activity 24 | import android.os.Build 25 | import androidx.compose.foundation.isSystemInDarkTheme 26 | import androidx.compose.material3.* 27 | import androidx.compose.runtime.Composable 28 | import androidx.compose.runtime.SideEffect 29 | import androidx.compose.ui.graphics.toArgb 30 | import androidx.compose.ui.platform.LocalContext 31 | import androidx.compose.ui.platform.LocalView 32 | import androidx.core.view.WindowCompat 33 | 34 | private val LightColors = lightColorScheme( 35 | primary = md_theme_light_primary, 36 | onPrimary = md_theme_light_onPrimary, 37 | primaryContainer = md_theme_light_primaryContainer, 38 | onPrimaryContainer = md_theme_light_onPrimaryContainer, 39 | inversePrimary = md_theme_light_inversePrimary, 40 | secondary = md_theme_light_secondary, 41 | onSecondary = md_theme_light_onSecondary, 42 | secondaryContainer = md_theme_light_secondaryContainer, 43 | onSecondaryContainer = md_theme_light_onSecondaryContainer, 44 | tertiary = md_theme_light_tertiary, 45 | onTertiary = md_theme_light_onTertiary, 46 | tertiaryContainer = md_theme_light_tertiaryContainer, 47 | onTertiaryContainer = md_theme_light_onTertiaryContainer, 48 | background = md_theme_light_background, 49 | onBackground = md_theme_light_onBackground, 50 | surface = md_theme_light_surface, 51 | onSurface = md_theme_light_onSurface, 52 | surfaceVariant = md_theme_light_surfaceVariant, 53 | onSurfaceVariant = md_theme_light_onSurfaceVariant, 54 | surfaceTint = md_theme_light_surfaceTint, 55 | inverseSurface = md_theme_light_inverseSurface, 56 | inverseOnSurface = md_theme_light_inverseOnSurface, 57 | error = md_theme_light_error, 58 | onError = md_theme_light_onError, 59 | errorContainer = md_theme_light_errorContainer, 60 | onErrorContainer = md_theme_light_onErrorContainer, 61 | outline = md_theme_light_outline, 62 | //surfaceTintColor = md_theme_light_surfaceTintColor, 63 | ) 64 | 65 | 66 | private val DarkColors = darkColorScheme( 67 | primary = md_theme_dark_primary, 68 | onPrimary = md_theme_dark_onPrimary, 69 | primaryContainer = md_theme_dark_primaryContainer, 70 | onPrimaryContainer = md_theme_dark_onPrimaryContainer, 71 | secondary = md_theme_dark_secondary, 72 | onSecondary = md_theme_dark_onSecondary, 73 | secondaryContainer = md_theme_dark_secondaryContainer, 74 | onSecondaryContainer = md_theme_dark_onSecondaryContainer, 75 | tertiary = md_theme_dark_tertiary, 76 | onTertiary = md_theme_dark_onTertiary, 77 | tertiaryContainer = md_theme_dark_tertiaryContainer, 78 | onTertiaryContainer = md_theme_dark_onTertiaryContainer, 79 | error = md_theme_dark_error, 80 | errorContainer = md_theme_dark_errorContainer, 81 | onError = md_theme_dark_onError, 82 | onErrorContainer = md_theme_dark_onErrorContainer, 83 | background = md_theme_dark_background, 84 | onBackground = md_theme_dark_onBackground, 85 | surface = md_theme_dark_surface, 86 | onSurface = md_theme_dark_onSurface, 87 | surfaceVariant = md_theme_dark_surfaceVariant, 88 | onSurfaceVariant = md_theme_dark_onSurfaceVariant, 89 | outline = md_theme_dark_outline, 90 | inverseOnSurface = md_theme_dark_inverseOnSurface, 91 | inverseSurface = md_theme_dark_inverseSurface, 92 | inversePrimary = md_theme_dark_inversePrimary, 93 | surfaceTint = md_theme_dark_surfaceTint, 94 | //surfaceTintColor = md_theme_dark_surfaceTintColor, 95 | ) 96 | 97 | @Composable 98 | fun OwlTheme( 99 | isDarkTheme: Boolean = isSystemInDarkTheme(), 100 | isPreviewing: Boolean = false, 101 | isDynamicColor: Boolean, 102 | content: @Composable () -> Unit 103 | ) { 104 | val isDynamicColorReadyDevice = isDynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S 105 | 106 | val colors = when { 107 | isDynamicColorReadyDevice && isDarkTheme -> dynamicDarkColorScheme(LocalContext.current) 108 | isDynamicColorReadyDevice && !isDarkTheme -> dynamicLightColorScheme(LocalContext.current) 109 | isDarkTheme -> DarkColors 110 | else -> LightColors 111 | } 112 | 113 | if (!isPreviewing) { 114 | val activity = LocalView.current.context as Activity 115 | SideEffect { 116 | activity.window.statusBarColor = colors.surface.toArgb() 117 | activity.window.navigationBarColor = colors.surface.toArgb() 118 | val wic = WindowCompat.getInsetsController(activity.window, activity.window.decorView) 119 | wic.isAppearanceLightStatusBars = !isDarkTheme 120 | wic.isAppearanceLightNavigationBars = !isDarkTheme 121 | } 122 | } 123 | 124 | MaterialTheme( 125 | colorScheme = colors, 126 | typography = AppTypography, 127 | content = content 128 | ) 129 | } 130 | 131 | @Suppress("unused") 132 | @Composable 133 | fun PreviewTheme( 134 | isDarkTheme: Boolean = isSystemInDarkTheme(), 135 | content: @Composable () -> Unit 136 | ) { 137 | OwlTheme( 138 | isDarkTheme = isDarkTheme, 139 | isPreviewing = true, 140 | isDynamicColor = false, 141 | content = content 142 | ) 143 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/yamin8000/owl/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl: an android app for Owlbot Dictionary API 3 | * Type.kt Created by Yamin Siahmargooei at 2022/6/16 4 | * This file is part of Owl. 5 | * Copyright (C) 2022 Yamin Siahmargooei 6 | * 7 | * Owl is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * Owl is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Owl. If not, see . 19 | */ 20 | 21 | package io.github.yamin8000.owl.ui.theme 22 | 23 | import androidx.compose.material3.Typography 24 | import androidx.compose.ui.text.TextStyle 25 | import androidx.compose.ui.text.font.Font 26 | import androidx.compose.ui.text.font.FontFamily 27 | import androidx.compose.ui.text.font.FontWeight 28 | import androidx.compose.ui.unit.sp 29 | import io.github.yamin8000.owl.R 30 | 31 | val Samim = FontFamily(Font(R.font.samimbold)) 32 | 33 | val Roboto = FontFamily.Default 34 | 35 | val AppTypography = Typography( 36 | labelLarge = TextStyle( 37 | fontFamily = Roboto, 38 | fontWeight = FontWeight.Medium, 39 | letterSpacing = 0.10000000149011612.sp, 40 | lineHeight = 20.sp, 41 | fontSize = 14.sp 42 | ), 43 | labelMedium = TextStyle( 44 | fontFamily = Roboto, 45 | fontWeight = FontWeight.Medium, 46 | letterSpacing = 0.5.sp, 47 | lineHeight = 16.sp, 48 | fontSize = 12.sp 49 | ), 50 | labelSmall = TextStyle( 51 | fontFamily = Roboto, 52 | fontWeight = FontWeight.Medium, 53 | letterSpacing = 0.5.sp, 54 | lineHeight = 16.sp, 55 | fontSize = 11.sp 56 | ), 57 | bodyLarge = TextStyle( 58 | fontFamily = Roboto, 59 | fontWeight = FontWeight.W400, 60 | letterSpacing = 0.5.sp, 61 | lineHeight = 24.sp, 62 | fontSize = 16.sp 63 | ), 64 | bodyMedium = TextStyle( 65 | fontFamily = Roboto, 66 | fontWeight = FontWeight.W400, 67 | letterSpacing = 0.25.sp, 68 | lineHeight = 20.sp, 69 | fontSize = 14.sp 70 | ), 71 | bodySmall = TextStyle( 72 | fontFamily = Roboto, 73 | fontWeight = FontWeight.W400, 74 | letterSpacing = 0.4000000059604645.sp, 75 | lineHeight = 16.sp, 76 | fontSize = 12.sp 77 | ), 78 | headlineLarge = TextStyle( 79 | fontFamily = Roboto, 80 | fontWeight = FontWeight.W400, 81 | letterSpacing = 0.sp, 82 | lineHeight = 40.sp, 83 | fontSize = 32.sp 84 | ), 85 | headlineMedium = TextStyle( 86 | fontFamily = Roboto, 87 | fontWeight = FontWeight.W400, 88 | letterSpacing = 0.sp, 89 | lineHeight = 36.sp, 90 | fontSize = 28.sp 91 | ), 92 | headlineSmall = TextStyle( 93 | fontFamily = Roboto, 94 | fontWeight = FontWeight.W400, 95 | letterSpacing = 0.sp, 96 | lineHeight = 32.sp, 97 | fontSize = 24.sp 98 | ), 99 | displayLarge = TextStyle( 100 | fontFamily = Roboto, 101 | fontWeight = FontWeight.W400, 102 | letterSpacing = (-0.25).sp, 103 | lineHeight = 64.sp, 104 | fontSize = 57.sp 105 | ), 106 | displayMedium = TextStyle( 107 | fontFamily = Roboto, 108 | fontWeight = FontWeight.W400, 109 | letterSpacing = 0.sp, 110 | lineHeight = 52.sp, 111 | fontSize = 45.sp 112 | ), 113 | displaySmall = TextStyle( 114 | fontFamily = Roboto, 115 | fontWeight = FontWeight.W400, 116 | letterSpacing = 0.sp, 117 | lineHeight = 44.sp, 118 | fontSize = 36.sp 119 | ), 120 | titleLarge = TextStyle( 121 | fontFamily = Roboto, 122 | fontWeight = FontWeight.W400, 123 | letterSpacing = 0.sp, 124 | lineHeight = 28.sp, 125 | fontSize = 22.sp 126 | ), 127 | titleMedium = TextStyle( 128 | fontFamily = Roboto, 129 | fontWeight = FontWeight.Medium, 130 | letterSpacing = 0.15000000596046448.sp, 131 | lineHeight = 24.sp, 132 | fontSize = 16.sp 133 | ), 134 | titleSmall = TextStyle( 135 | fontFamily = Roboto, 136 | fontWeight = FontWeight.Medium, 137 | letterSpacing = 0.10000000149011612.sp, 138 | lineHeight = 20.sp, 139 | fontSize = 14.sp 140 | ), 141 | ) -------------------------------------------------------------------------------- /app/src/main/java/io/github/yamin8000/owl/ui/util/DynamicColorPalette.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl: an android app for Owlbot Dictionary API 3 | * DynamicColorPalette.kt Created by Yamin Siahmargooei at 2022/9/26 4 | * This file is part of Owl. 5 | * Copyright (C) 2022 Yamin Siahmargooei 6 | * 7 | * Owl is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * Owl is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Owl. If not, see . 19 | */ 20 | 21 | package io.github.yamin8000.owl.ui.util 22 | 23 | import android.content.Context 24 | import androidx.collection.LruCache 25 | import androidx.compose.animation.animateColorAsState 26 | import androidx.compose.animation.core.Spring 27 | import androidx.compose.animation.core.spring 28 | import androidx.compose.material3.MaterialTheme 29 | import androidx.compose.runtime.* 30 | import androidx.compose.ui.graphics.Color 31 | import androidx.compose.ui.platform.LocalContext 32 | import androidx.core.graphics.drawable.toBitmap 33 | import androidx.palette.graphics.Palette 34 | import coil.imageLoader 35 | import coil.request.ImageRequest 36 | import coil.request.SuccessResult 37 | import coil.size.Scale 38 | import kotlinx.coroutines.Dispatchers 39 | import kotlinx.coroutines.withContext 40 | 41 | @Composable 42 | fun rememberDominantColorState( 43 | context: Context = LocalContext.current, 44 | defaultColor: Color = MaterialTheme.colorScheme.surfaceVariant, 45 | defaultOnColor: Color = MaterialTheme.colorScheme.onSurfaceVariant, 46 | cacheSize: Int = 12, 47 | isColorValid: (Color) -> Boolean = { true } 48 | ): DominantColorState = remember { 49 | DominantColorState(context, defaultColor, defaultOnColor, cacheSize, isColorValid) 50 | } 51 | 52 | /** 53 | * A composable which allows dynamic theming of the [androidx.compose.material.Colors.primary] 54 | * color from an image. 55 | */ 56 | @Composable 57 | fun DynamicThemePrimaryColorsFromImage( 58 | dominantColorState: DominantColorState = rememberDominantColorState(), 59 | content: @Composable () -> Unit 60 | ) { 61 | val colors = MaterialTheme.colorScheme.copy( 62 | primary = animateColorAsState( 63 | dominantColorState.color, 64 | spring(stiffness = Spring.StiffnessLow) 65 | ).value, 66 | onPrimary = animateColorAsState( 67 | dominantColorState.onColor, 68 | spring(stiffness = Spring.StiffnessLow) 69 | ).value 70 | ) 71 | MaterialTheme(colorScheme = colors, content = content) 72 | } 73 | 74 | /** 75 | * A class which stores and caches the result of any calculated dominant colors 76 | * from images. 77 | * 78 | * @param context Android context 79 | * @param defaultColor The default color, which will be used if [calculateDominantColor] fails to 80 | * calculate a dominant color 81 | * @param defaultOnColor The default foreground 'on color' for [defaultColor]. 82 | * @param cacheSize The size of the [LruCache] used to store recent results. Pass `0` to 83 | * disable the cache. 84 | * @param isColorValid A lambda which allows filtering of the calculated image colors. 85 | */ 86 | @Stable 87 | class DominantColorState( 88 | private val context: Context, 89 | private val defaultColor: Color, 90 | private val defaultOnColor: Color, 91 | cacheSize: Int = 12, 92 | private val isColorValid: (Color) -> Boolean = { true } 93 | ) { 94 | var color by mutableStateOf(defaultColor) 95 | private set 96 | var onColor by mutableStateOf(defaultOnColor) 97 | private set 98 | 99 | private val cache = when { 100 | cacheSize > 0 -> LruCache(cacheSize) 101 | else -> null 102 | } 103 | 104 | suspend fun updateColorsFromImageUrl(url: String) { 105 | val result = calculateDominantColor(url) 106 | color = result?.color ?: defaultColor 107 | onColor = result?.onColor ?: defaultOnColor 108 | } 109 | 110 | private suspend fun calculateDominantColor(url: String): DominantColors? { 111 | val cached = cache?.get(url) 112 | if (cached != null) { 113 | // If we already have the result cached, return early now... 114 | return cached 115 | } 116 | 117 | // Otherwise we calculate the swatches in the image, and return the first valid color 118 | return calculateSwatchesInImage(context, url) 119 | .filter { swatch -> 120 | val lightness = swatch.hsl[2] 121 | val saturation = swatch.hsl[1] 122 | lightness > .1f && lightness <= .9f && saturation >= .1f 123 | } 124 | // First we want to sort the list by the color's population 125 | .sortedByDescending { swatch -> swatch.population } 126 | // Then we want to find the first valid color 127 | .firstOrNull { swatch -> isColorValid(Color(swatch.rgb)) } 128 | // If we found a valid swatch, wrap it in a [DominantColors] 129 | ?.let { swatch -> 130 | DominantColors( 131 | color = Color(swatch.rgb), 132 | onColor = Color(swatch.bodyTextColor).copy(alpha = 1f) 133 | ) 134 | } 135 | // Cache the resulting [DominantColors] 136 | ?.also { result -> cache?.put(url, result) } 137 | } 138 | 139 | /** 140 | * Reset the color values to [defaultColor]. 141 | */ 142 | fun reset() { 143 | color = defaultColor 144 | onColor = defaultOnColor 145 | } 146 | } 147 | 148 | @Immutable 149 | private data class DominantColors(val color: Color, val onColor: Color) 150 | 151 | /** 152 | * Fetches the given [imageUrl] with Coil, then uses [Palette] to calculate the dominant color. 153 | */ 154 | private suspend fun calculateSwatchesInImage( 155 | context: Context, 156 | imageUrl: String 157 | ): List { 158 | val request = ImageRequest.Builder(context) 159 | .data(imageUrl) 160 | // We scale the image to cover 128px x 128px (i.e. min dimension == 128px) 161 | .size(128).scale(Scale.FILL) 162 | // Disable hardware bitmaps, since Palette uses Bitmap.getPixels() 163 | .allowHardware(false) 164 | // Set a custom memory cache key to avoid overwriting the displayed image in the cache 165 | .memoryCacheKey("$imageUrl.palette") 166 | .build() 167 | 168 | val bitmap = when (val result = context.imageLoader.execute(request)) { 169 | is SuccessResult -> result.drawable.toBitmap() 170 | else -> null 171 | } 172 | 173 | return bitmap?.let { 174 | withContext(Dispatchers.Default) { 175 | val palette = Palette.Builder(bitmap) 176 | // Disable any bitmap resizing in Palette. We've already loaded an appropriately 177 | // sized bitmap through Coil 178 | .resizeBitmapArea(0) 179 | // Clear any built-in filters. We want the unfiltered dominant color 180 | .clearFilters() 181 | // We reduce the maximum color count down to 8 182 | .maximumColorCount(8) 183 | .generate() 184 | 185 | palette.swatches 186 | } 187 | } ?: emptyList() 188 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/yamin8000/owl/util/AutoCompleteHelper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl: an android app for Owlbot Dictionary API 3 | * AutoCompleteHelper.kt Created by Yamin Siahmargooei at 2022/10/22 4 | * This file is part of Owl. 5 | * Copyright (C) 2022 Yamin Siahmargooei 6 | * 7 | * Owl is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * Owl is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Owl. If not, see . 19 | */ 20 | 21 | package io.github.yamin8000.owl.util 22 | 23 | import android.content.Context 24 | import android.content.res.Resources.NotFoundException 25 | import io.github.yamin8000.owl.R 26 | import io.github.yamin8000.owl.util.Constants.DEFAULT_N_GRAM_SIZE 27 | import io.github.yamin8000.owl.util.Constants.NOT_WORD_CHARS_REGEX 28 | import io.github.yamin8000.owl.util.Constants.db 29 | import kotlinx.coroutines.CoroutineScope 30 | import kotlinx.coroutines.launch 31 | import kotlin.math.abs 32 | import kotlin.math.ceil 33 | import kotlin.math.roundToInt 34 | 35 | class AutoCompleteHelper( 36 | private val context: Context, 37 | coroutineScope: CoroutineScope, 38 | userData: List = listOf() 39 | ) { 40 | private var data = setOf() 41 | 42 | init { 43 | data = getBasic2000Data().plus(userData).toSet() 44 | coroutineScope.launch { data = data.plus(getOldSearchData()) } 45 | } 46 | 47 | private fun getBasic2000Data() = try { 48 | context.resources.openRawResource(R.raw.basic2000) 49 | .bufferedReader() 50 | .use { it.readText() } 51 | .split(',') 52 | .map { it.replace(NOT_WORD_CHARS_REGEX, "") } 53 | } catch (e: NotFoundException) { 54 | listOf() 55 | } 56 | 57 | suspend fun suggestTermsForSearch( 58 | searchTerm: String 59 | ): List { 60 | data = data.plus(getOldSearchData()) 61 | 62 | val term = searchTerm.lowercase().replace(NOT_WORD_CHARS_REGEX, "") 63 | val nGramSize = nGramSizeProvider(term) 64 | if (data.contains(term)) return listOf(term) 65 | val searchTermGrams = term.windowed(nGramSize) 66 | val suggestions = buildSet { 67 | searchTermGrams.forEach { gram -> 68 | addAll(data.filter { word -> word.contains(gram) }) 69 | } 70 | } 71 | return sortSuggestions(suggestions, term) 72 | } 73 | 74 | private fun sortSuggestions( 75 | suggestions: Set, 76 | searchTerm: String 77 | ): List { 78 | val nGramSize = nGramSizeProvider(searchTerm) 79 | if (suggestions.isNotEmpty() && suggestions.size > 1) { 80 | val searchTermGrams = searchTerm.windowed(nGramSize) 81 | val rankedSuggestions = buildList { 82 | suggestions.forEach { suggestion -> 83 | val rank = suggestion.windowed(nGramSize) 84 | .intersect(searchTermGrams.toSet()) 85 | .size 86 | add(rank to suggestion) 87 | } 88 | } 89 | return rankedSuggestions.asSequence() 90 | .sortedBy { abs(it.second.length - searchTerm.length) } 91 | .sortedByDescending { 92 | it.second.startsWith(searchTerm.take(nGramSize)) || 93 | it.second.endsWith(searchTerm.takeLast(nGramSize)) 94 | } 95 | .sortedByDescending { it.first } 96 | .map { it.second } 97 | .toList() 98 | } else return suggestions.toList() 99 | } 100 | 101 | private fun nGramSizeProvider( 102 | searchTerm: String 103 | ): Int { 104 | return if (searchTerm.length > DEFAULT_N_GRAM_SIZE) { 105 | val size = ceil(searchTerm.length.toFloat() / DEFAULT_N_GRAM_SIZE).roundToInt() 106 | if (size < DEFAULT_N_GRAM_SIZE) DEFAULT_N_GRAM_SIZE 107 | else size 108 | } else DEFAULT_N_GRAM_SIZE 109 | } 110 | 111 | private suspend fun getOldSearchData() = db.wordDao().getAll().map { it.word } 112 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/yamin8000/owl/util/Constants.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl: an android app for Owlbot Dictionary API 3 | * Constants.kt Created by Yamin Siahmargooei at 2022/1/16 4 | * This file is part of Owl. 5 | * Copyright (C) 2022 Yamin Siahmargooei 6 | * 7 | * Owl is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * Owl is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Owl. If not, see . 19 | */ 20 | 21 | package io.github.yamin8000.owl.util 22 | 23 | import io.github.yamin8000.owl.db.AppDatabase 24 | 25 | object Constants { 26 | lateinit var db: AppDatabase 27 | const val LOG_TAG = "<==>" 28 | 29 | const val IMAGE_URL = "image_url" 30 | const val THEME = "theme" 31 | const val TTS_LANG = "tts_lang" 32 | const val IS_VIBRATING = "is_vibrating" 33 | 34 | const val DEFAULT_N_GRAM_SIZE = 3 35 | 36 | val NOT_WORD_CHARS_REGEX = Regex("\\W+") 37 | 38 | const val INTERNET_CHECK_DELAY = 3000L 39 | val DNS_SERVERS = listOf( 40 | "8.8.8.8", 41 | "8.8.4.4", 42 | "1.1.1.1", 43 | "1.0.0.1", 44 | "185.51.200.2", 45 | "178.22.122.100", 46 | "10.202.10.202", 47 | "10.202.10.102" 48 | ) 49 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/yamin8000/owl/util/DataStoreHelper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl: an android app for Owlbot Dictionary API 3 | * DataStoreHelper.kt Created by Yamin Siahmargooei at 2022/9/20 4 | * This file is part of Owl. 5 | * Copyright (C) 2022 Yamin Siahmargooei 6 | * 7 | * Owl is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * Owl is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Owl. If not, see . 19 | */ 20 | 21 | package io.github.yamin8000.owl.util 22 | 23 | import androidx.datastore.core.DataStore 24 | import androidx.datastore.preferences.core.* 25 | import kotlinx.coroutines.flow.firstOrNull 26 | import kotlinx.coroutines.flow.map 27 | 28 | @Suppress("unused") 29 | class DataStoreHelper( 30 | private val datastore: DataStore 31 | ) { 32 | 33 | suspend fun getString( 34 | key: String 35 | ) = datastore.data.map { 36 | it[stringPreferencesKey(key)] 37 | }.firstOrNull() 38 | 39 | suspend fun setString( 40 | key: String, 41 | value: String 42 | ) { 43 | datastore.edit { 44 | it[stringPreferencesKey(key)] = value 45 | } 46 | } 47 | 48 | suspend fun getInt( 49 | key: String 50 | ) = datastore.data.map { 51 | it[intPreferencesKey(key)] 52 | }.firstOrNull() 53 | 54 | suspend fun setInt( 55 | key: String, 56 | value: Int 57 | ) { 58 | datastore.edit { 59 | it[intPreferencesKey(key)] = value 60 | } 61 | } 62 | 63 | suspend fun getBool( 64 | key: String 65 | ) = datastore.data.map { 66 | it[booleanPreferencesKey(key)] 67 | }.firstOrNull() 68 | 69 | suspend fun setBool( 70 | key: String, 71 | value: Boolean 72 | ) { 73 | datastore.edit { 74 | it[booleanPreferencesKey(key)] = value 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/yamin8000/owl/util/RecomposeHighlighter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl: an android app for Owlbot Dictionary API 3 | * RecomposeHighlighter.kt Created by Yamin Siahmargooei at 2022/10/15 4 | * This file is part of Owl. 5 | * Copyright (C) 2022 Yamin Siahmargooei 6 | * 7 | * Owl is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * Owl is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Owl. If not, see . 19 | */ 20 | 21 | package io.github.yamin8000.owl.util 22 | 23 | import androidx.compose.runtime.LaunchedEffect 24 | import androidx.compose.runtime.Stable 25 | import androidx.compose.runtime.mutableStateOf 26 | import androidx.compose.runtime.remember 27 | import androidx.compose.ui.Modifier 28 | import androidx.compose.ui.composed 29 | import androidx.compose.ui.draw.drawWithCache 30 | import androidx.compose.ui.geometry.Offset 31 | import androidx.compose.ui.geometry.Size 32 | import androidx.compose.ui.graphics.Color 33 | import androidx.compose.ui.graphics.SolidColor 34 | import androidx.compose.ui.graphics.drawscope.Fill 35 | import androidx.compose.ui.graphics.drawscope.Stroke 36 | import androidx.compose.ui.graphics.lerp 37 | import androidx.compose.ui.platform.debugInspectorInfo 38 | import androidx.compose.ui.unit.dp 39 | import kotlinx.coroutines.delay 40 | import kotlin.math.min 41 | 42 | /** 43 | * A [Modifier] that draws a border around elements that are recomposing. The border increases in 44 | * size and interpolates from red to green as more recompositions occur before a timeout. 45 | */ 46 | @Stable 47 | fun Modifier.recomposeHighlighter(): Modifier = this.then(recomposeModifier) 48 | 49 | // Use a single instance + @Stable to ensure that recompositions can enable skipping optimizations 50 | // Modifier.composed will still remember unique data per call site. 51 | private val recomposeModifier = 52 | Modifier.composed(inspectorInfo = debugInspectorInfo { name = "recomposeHighlighter" }) { 53 | // The total number of compositions that have occurred. We're not using a State<> here be 54 | // able to read/write the value without invalidating (which would cause infinite 55 | // recomposition). 56 | val totalCompositions = remember { arrayOf(0L) } 57 | totalCompositions[0]++ 58 | 59 | // The value of totalCompositions at the last timeout. 60 | val totalCompositionsAtLastTimeout = remember { mutableStateOf(0L) } 61 | 62 | // Start the timeout, and reset everytime there's a recomposition. (Using totalCompositions 63 | // as the key is really just to cause the timer to restart every composition). 64 | LaunchedEffect(totalCompositions[0]) { 65 | delay(3000) 66 | totalCompositionsAtLastTimeout.value = totalCompositions[0] 67 | } 68 | 69 | Modifier.drawWithCache { 70 | onDrawWithContent { 71 | // Draw actual content. 72 | drawContent() 73 | 74 | // Below is to draw the highlight, if necessary. A lot of the logic is copied from 75 | // Modifier.border 76 | val numCompositionsSinceTimeout = 77 | totalCompositions[0] - totalCompositionsAtLastTimeout.value 78 | 79 | val hasValidBorderParams = size.minDimension > 0f 80 | if (!hasValidBorderParams || numCompositionsSinceTimeout <= 0) { 81 | return@onDrawWithContent 82 | } 83 | 84 | val (color, strokeWidthPx) = 85 | when (numCompositionsSinceTimeout) { 86 | // We need at least one composition to draw, so draw the smallest border 87 | // color in blue. 88 | 1L -> Color.Blue to 1f 89 | // 2 compositions is _probably_ okay. 90 | 2L -> Color.Green to 2.dp.toPx() 91 | // 3 or more compositions before timeout may indicate an issue. lerp the 92 | // color from yellow to red, and continually increase the border size. 93 | else -> { 94 | lerp( 95 | Color.Yellow.copy(alpha = 0.8f), 96 | Color.Red.copy(alpha = 0.5f), 97 | min(1f, (numCompositionsSinceTimeout - 1).toFloat() / 100f) 98 | ) to numCompositionsSinceTimeout.toInt().dp.toPx() 99 | } 100 | } 101 | 102 | val halfStroke = strokeWidthPx / 2 103 | val topLeft = Offset(halfStroke, halfStroke) 104 | val borderSize = Size(size.width - strokeWidthPx, size.height - strokeWidthPx) 105 | 106 | val fillArea = (strokeWidthPx * 2) > size.minDimension 107 | val rectTopLeft = if (fillArea) Offset.Zero else topLeft 108 | val size = if (fillArea) size else borderSize 109 | val style = if (fillArea) Fill else Stroke(strokeWidthPx) 110 | 111 | drawRect( 112 | brush = SolidColor(color), 113 | topLeft = rectTopLeft, 114 | size = size, 115 | style = style 116 | ) 117 | } 118 | } 119 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/yamin8000/owl/util/TTS.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl: an android app for Owlbot Dictionary API 3 | * TTS.kt Created by Yamin Siahmargooei at 2022/10/12 4 | * This file is part of Owl. 5 | * Copyright (C) 2022 Yamin Siahmargooei 6 | * 7 | * Owl is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * Owl is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Owl. If not, see . 19 | */ 20 | 21 | package io.github.yamin8000.owl.util 22 | 23 | import android.content.Context 24 | import android.speech.tts.TextToSpeech 25 | import kotlinx.coroutines.suspendCancellableCoroutine 26 | import java.util.* 27 | import kotlin.coroutines.resume 28 | 29 | class TTS( 30 | private val context: Context, 31 | private val locale: Locale = Locale.US, 32 | ) { 33 | private lateinit var tts: TextToSpeech 34 | private var ttsLang: Int = 0 35 | 36 | suspend fun getTts(): TextToSpeech? { 37 | return suspendCancellableCoroutine { continuation -> 38 | tts = TextToSpeech(context) { 39 | if (it == TextToSpeech.SUCCESS) { 40 | ttsLang = tts.setLanguage(locale) 41 | if (ttsLang == TextToSpeech.LANG_NOT_SUPPORTED) { 42 | continuation.resume(null) 43 | } else continuation.resume(tts) 44 | } else continuation.resume(null) 45 | } 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/yamin8000/owl/util/Utility.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl: an android app for Owlbot Dictionary API 3 | * Utility.kt Created by Yamin Siahmargooei at 2022/8/22 4 | * This file is part of Owl. 5 | * Copyright (C) 2022 Yamin Siahmargooei 6 | * 7 | * Owl is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * Owl is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Owl. If not, see . 19 | */ 20 | 21 | package io.github.yamin8000.owl.util 22 | 23 | import android.app.Activity 24 | import android.content.Context 25 | import android.content.ContextWrapper 26 | import android.os.Build 27 | import android.speech.tts.TextToSpeech 28 | import android.util.Log 29 | import androidx.compose.runtime.Immutable 30 | import androidx.compose.runtime.Stable 31 | import androidx.compose.runtime.saveable.Saver 32 | import io.github.yamin8000.owl.BuildConfig 33 | import io.github.yamin8000.owl.model.Definition 34 | import java.util.* 35 | 36 | fun Context.findActivity(): Activity? = when (this) { 37 | is Activity -> this 38 | is ContextWrapper -> baseContext.findActivity() 39 | else -> null 40 | } 41 | 42 | @Suppress("DEPRECATION") 43 | fun getCurrentLocale( 44 | context: Context 45 | ): Locale { 46 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 47 | context.resources.configuration.locales.get(0) 48 | } else context.resources.configuration.locale 49 | } 50 | 51 | fun TextToSpeech.speak( 52 | text: String 53 | ) { 54 | speak(text, TextToSpeech.QUEUE_FLUSH, null, null) 55 | } 56 | 57 | @Suppress("unused") 58 | @Stable 59 | class StableHolder(val item: T) { 60 | operator fun component1(): T = item 61 | } 62 | 63 | @Immutable 64 | class ImmutableHolder(val item: T) { 65 | operator fun component1(): T = item 66 | } 67 | 68 | val DefinitionListSaver = getImmutableHolderSaver>() 69 | 70 | fun getImmutableHolderSaver(): Saver, T> = Saver( 71 | save = { it.item }, 72 | restore = { ImmutableHolder(it) } 73 | ) 74 | 75 | fun log( 76 | message: String 77 | ) { 78 | if (BuildConfig.DEBUG) 79 | Log.d(Constants.LOG_TAG, message) 80 | } 81 | 82 | fun log( 83 | exception: Exception 84 | ) { 85 | log(exception.stackTraceToString()) 86 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/yamin8000/owl/util/list/ListSatiation.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl/Owl.app.main 3 | * ListSatiation.kt Copyrighted by Yamin Siahmargooei at 2023/1/7 4 | * ListSatiation.kt Last modified at 2023/1/7 5 | * This file is part of Owl/Owl.app.main. 6 | * Copyright (C) 2023 Yamin Siahmargooei 7 | * 8 | * Owl/Owl.app.main is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * Owl/Owl.app.main is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with Owl. If not, see . 20 | */ 21 | 22 | package io.github.yamin8000.owl.util.list 23 | 24 | enum class ListSatiation { 25 | Empty, Partial 26 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 20 | 21 | 26 | 29 | 34 | 39 | 44 | 49 | 54 | 59 | 64 | 69 | 74 | 79 | 84 | 89 | 94 | 99 | 104 | 109 | 114 | 119 | 124 | 129 | 134 | 139 | 144 | 149 | 154 | 159 | 164 | 169 | 174 | 179 | 184 | 189 | 190 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 20 | 25 | 30 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/font/samimbold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yamin8000/Owl2/abf80da26053409758c41c5d2b4e88116d42fba4/app/src/main/res/font/samimbold.ttf -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yamin8000/Owl2/abf80da26053409758c41c5d2b4e88116d42fba4/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yamin8000/Owl2/abf80da26053409758c41c5d2b4e88116d42fba4/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yamin8000/Owl2/abf80da26053409758c41c5d2b4e88116d42fba4/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yamin8000/Owl2/abf80da26053409758c41c5d2b4e88116d42fba4/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yamin8000/Owl2/abf80da26053409758c41c5d2b4e88116d42fba4/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yamin8000/Owl2/abf80da26053409758c41c5d2b4e88116d42fba4/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yamin8000/Owl2/abf80da26053409758c41c5d2b4e88116d42fba4/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yamin8000/Owl2/abf80da26053409758c41c5d2b4e88116d42fba4/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yamin8000/Owl2/abf80da26053409758c41c5d2b4e88116d42fba4/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yamin8000/Owl2/abf80da26053409758c41c5d2b4e88116d42fba4/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values-fa/strings.xml: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | جغدک 23 | جستجو 24 | واژه‌ای برای جستجو بنویسید 25 | واژه‌های برگزیده 26 | جستجوهای پیشین 27 | واژه تصادفی 28 | خطا در برقراری ارتباط با اینترنت، خطا ممکن است به خاطر محدودیت های اینترنت باشد 29 | سرور برنامه تحت فشار است، لطفا چند دقیقه دیگر تلاش کنید 30 | توضیحی برای این کلمه یافت نشد 31 | خطای احراز هویت با سرور برنامه 32 | متن کپی شد 33 | این یک برنامه آزاد طبق پروانه گنو است که توضیحات بیشتر آن در ادامه آورده شده است 34 | لوگو پروانه برنامه GPLv3 35 | فرهنگ واژگان انگلیسی به انگلیسی جغد، برگرفته از فرهنگ واژگان 36 | به برگزیده‌ها اضافه شد 37 | پاک کردن 38 | خطای پیش بینی نشده 39 | عبارتی وارد نشده 40 | اشتراک گذاری 41 | این متن توسط برنامه جغدک تولید شده 42 | پاک کردن همه 43 | تنظیمات 44 | زمینه 45 | در نسخه های اندروید 12 به بعد زمینه برنامه بر اساس رنگ قالب تصویر پس زمینه گوشی شما تعیین می شود 46 | هماهنگ سیستم 47 | روشن 48 | تاریک 49 | زبان متن به گفتار 50 | درباره 51 | نگارش 52 | تلفظ 53 | واژه 54 | اموجی 55 | نمونه 56 | معنی 57 | نوع 58 | عمومی 59 | لرزش در زمان پیمایش لیست 60 | لغو 61 | این واژه پیش از این به برگزیده‌ها اضافه شده است. 62 | پاک کردن 63 | صدای بلندگو را زیاد کنید. 64 | -------------------------------------------------------------------------------- /app/src/main/res/values-hu/strings.xml: -------------------------------------------------------------------------------- 1 | 21 | 22 | 23 | Owl 24 | Keresés 25 | Írj be egy szót a kereséshez 26 | Kedvenc Szavak 27 | Keresési Előzmények 28 | Véletlenszerű Szó 29 | Nincs kapcsolat a szerverrel. Az adatok előfordulhat, hogy a gyorsítótárból kerülnek betöltésre. 30 | A szerver elfoglalt, próbáld újra! 31 | Nem található definíció a szóra. 32 | Hitelesítési Hiba 33 | Szöveg másolva. 34 | Ez egy ingyenes applikáció a GNU licensz alatt, tudj meg többet: 35 | GPLv3 Licensz Logó 36 | Angol-Angol szótár alapján: 37 | Hozzáadva a kedvencekhez 38 | Kiürítés 39 | Váratlan hiba! 40 | Semmi sem került bevitelre. 41 | Megosztás 42 | Ezt a szöveget az Owl app generálta 43 | Mind kiürítése 44 | Beállítások 45 | Téma 46 | Android 12-n és afelett az alkalmazásod témája a háttérképed színei alapján készül el 47 | Rendszer 48 | Világos 49 | Sötét 50 | Szövegből-Beszéd Nyelve 51 | Az alkalmazásról 52 | Verzió 53 | Kiejtés 54 | Szó 55 | Emoji 56 | Példa 57 | Definíció 58 | Típus 59 | Általános 60 | Rezgés görgetéskor 61 | Mégse 62 | Ez a szó már a kedvencek között van. 63 | Törlés 64 | Növeld meg a hangerődet 65 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 21 | 22 | 23 | #BB0055 24 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | Owl 23 | Search 24 | Enter a word to search 25 | Favourite Words 26 | Search History 27 | Random Word 28 | No connection to the server. Data maybe loaded from the cache. 29 | Server is busy, try again! 30 | No definition for the word found. 31 | Authentication Error 32 | Text copied. 33 | This is a free application under GNU license, see more: 34 | GPLv3 License Logo 35 | English to English Dictionary, based on: 36 | https://github.com/yamin8000/Owl2 37 | https://raw.githubusercontent.com/yamin8000/Owl2/master/LICENSE 38 | https://owlbot.info 39 | Added to favourites 40 | Clear 41 | Unexpected error! 42 | Nothing is entered. 43 | Share 44 | This text is generated using Owl app 45 | Clear All 46 | Settings 47 | Theme 48 | On Android 12+ your app theme colors are based on your home screen background 49 | System 50 | Light 51 | Dark 52 | Text-to-Speech Language 53 | About 54 | Version 55 | Pronunciation 56 | Word 57 | Emoji 58 | Example 59 | Definition 60 | Type 61 | This text is extracted from Owlbot Dictionary. 62 | General 63 | Vibrate on scroll 64 | Cancel 65 | This word is already added to the favorites. 66 | Delete 67 | Increase your volume 68 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | 23 | 25 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | compose_libs_version = '1.4.3' 4 | compose_ui_libs_version = '1.4.3' 5 | compose_compiler_version = '1.4.8' 6 | } 7 | }/* 8 | * Owl: an android app for Owlbot Dictionary API 9 | * build.gradle Created by Yamin Siahmargooei at 2022/6/16 10 | * This file is part of Owl. 11 | * Copyright (C) 2022 Yamin Siahmargooei 12 | * 13 | * Owl is free software: you can redistribute it and/or modify 14 | * it under the terms of the GNU General Public License as published by 15 | * the Free Software Foundation, either version 3 of the License, or 16 | * (at your option) any later version. 17 | * 18 | * Owl is distributed in the hope that it will be useful, 19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | * GNU General Public License for more details. 22 | * 23 | * You should have received a copy of the GNU General Public License 24 | * along with Owl. If not, see . 25 | */ 26 | 27 | plugins { 28 | id 'com.android.application' version '8.0.2' apply false 29 | id 'com.android.library' version '8.0.2' apply false 30 | id 'org.jetbrains.kotlin.android' version '1.8.22' apply false 31 | id 'com.google.devtools.ksp' version '1.8.22-1.0.11' 32 | } 33 | 34 | task clean(type: Delete) { 35 | delete rootProject.buildDir 36 | } 37 | 38 | subprojects { 39 | tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { 40 | kotlinOptions { 41 | freeCompilerArgs += [ 42 | "-P", 43 | "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" + 44 | project.buildDir.absolutePath + "/compose_metrics" 45 | ] 46 | freeCompilerArgs += [ 47 | "-P", 48 | "plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" + 49 | project.buildDir.absolutePath + "/compose_metrics" 50 | ] 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Owl: an android app for Owlbot Dictionary API 3 | # gradle.properties Created by Yamin Siahmargooei at 2022/6/16 4 | # This file is part of Owl. 5 | # Copyright (C) 2022 Yamin Siahmargooei 6 | # 7 | # Owl is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # Owl is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with Owl. If not, see . 19 | # 20 | # Project-wide Gradle settings. 21 | # IDE (e.g. Android Studio) users: 22 | # Gradle settings configured through the IDE *will override* 23 | # any settings specified in this file. 24 | # For more details on how to configure your build environment visit 25 | # http://www.gradle.org/docs/current/userguide/build_environment.html 26 | # Specifies the JVM arguments used for the daemon process. 27 | # The setting is particularly useful for tweaking memory settings. 28 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 29 | # When configured, Gradle will run in incubating parallel mode. 30 | # This option should only be used with decoupled projects. More details, visit 31 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 32 | # org.gradle.parallel=true 33 | # AndroidX package structure to make it clearer which packages are bundled with the 34 | # Android operating system, and which are packaged with your app"s APK 35 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 36 | android.useAndroidX=true 37 | # Kotlin code style for this project: "official" or "obsolete": 38 | kotlin.code.style=official 39 | # Enables namespacing of each library's R class so that its R class includes only the 40 | # resources declared in the library itself and none from the library's dependencies, 41 | # thereby reducing the size of the R class for that library 42 | android.enableR8.fullMode=false -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yamin8000/Owl2/abf80da26053409758c41c5d2b4e88116d42fba4/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jul 07 12:25:23 GMT+03:30 2023 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip 5 | networkTimeout=10000 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 87 | 88 | # Use the maximum available, or set MAX_FD != -1 to use that value. 89 | MAX_FD=maximum 90 | 91 | warn () { 92 | echo "$*" 93 | } >&2 94 | 95 | die () { 96 | echo 97 | echo "$*" 98 | echo 99 | exit 1 100 | } >&2 101 | 102 | # OS specific support (must be 'true' or 'false'). 103 | cygwin=false 104 | msys=false 105 | darwin=false 106 | nonstop=false 107 | case "$( uname )" in #( 108 | CYGWIN* ) cygwin=true ;; #( 109 | Darwin* ) darwin=true ;; #( 110 | MSYS* | MINGW* ) msys=true ;; #( 111 | NONSTOP* ) nonstop=true ;; 112 | esac 113 | 114 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 115 | 116 | 117 | # Determine the Java command to use to start the JVM. 118 | if [ -n "$JAVA_HOME" ] ; then 119 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 120 | # IBM's JDK on AIX uses strange locations for the executables 121 | JAVACMD=$JAVA_HOME/jre/sh/java 122 | else 123 | JAVACMD=$JAVA_HOME/bin/java 124 | fi 125 | if [ ! -x "$JAVACMD" ] ; then 126 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 127 | 128 | Please set the JAVA_HOME variable in your environment to match the 129 | location of your Java installation." 130 | fi 131 | else 132 | JAVACMD=java 133 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 134 | 135 | Please set the JAVA_HOME variable in your environment to match the 136 | location of your Java installation." 137 | fi 138 | 139 | # Increase the maximum file descriptors if we can. 140 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 141 | case $MAX_FD in #( 142 | max*) 143 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 144 | # shellcheck disable=SC3045 145 | MAX_FD=$( ulimit -H -n ) || 146 | warn "Could not query maximum file descriptor limit" 147 | esac 148 | case $MAX_FD in #( 149 | '' | soft) :;; #( 150 | *) 151 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 152 | # shellcheck disable=SC3045 153 | ulimit -n "$MAX_FD" || 154 | warn "Could not set maximum file descriptor limit to $MAX_FD" 155 | esac 156 | fi 157 | 158 | # Collect all arguments for the java command, stacking in reverse order: 159 | # * args from the command line 160 | # * the main class name 161 | # * -classpath 162 | # * -D...appname settings 163 | # * --module-path (only if needed) 164 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 165 | 166 | # For Cygwin or MSYS, switch paths to Windows format before running java 167 | if "$cygwin" || "$msys" ; then 168 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 169 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 170 | 171 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 172 | 173 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 174 | for arg do 175 | if 176 | case $arg in #( 177 | -*) false ;; # don't mess with options #( 178 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 179 | [ -e "$t" ] ;; #( 180 | *) false ;; 181 | esac 182 | then 183 | arg=$( cygpath --path --ignore --mixed "$arg" ) 184 | fi 185 | # Roll the args list around exactly as many times as the number of 186 | # args, so each arg winds up back in the position where it started, but 187 | # possibly modified. 188 | # 189 | # NB: a `for` loop captures its iteration list before it begins, so 190 | # changing the positional parameters here affects neither the number of 191 | # iterations, nor the values presented in `arg`. 192 | shift # remove old arg 193 | set -- "$@" "$arg" # push replacement arg 194 | done 195 | fi 196 | 197 | 198 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 199 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 200 | 201 | # Collect all arguments for the java command; 202 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 203 | # shell script including quotes and variable substitutions, so put them in 204 | # double quotes to make sure that they get re-expanded; and 205 | # * put everything else in single quotes, so that it's not re-expanded. 206 | 207 | set -- \ 208 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 209 | -classpath "$CLASSPATH" \ 210 | org.gradle.wrapper.GradleWrapperMain \ 211 | "$@" 212 | 213 | # Stop when "xargs" is not available. 214 | if ! command -v xargs >/dev/null 2>&1 215 | then 216 | die "xargs is not available" 217 | fi 218 | 219 | # Use "xargs" to parse quoted args. 220 | # 221 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 222 | # 223 | # In Bash we could simply go: 224 | # 225 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 226 | # set -- "${ARGS[@]}" "$@" 227 | # 228 | # but POSIX shell has neither arrays nor command substitution, so instead we 229 | # post-process each arg (as a line of input to sed) to backslash-escape any 230 | # character that might be a shell metacharacter, then use eval to reverse 231 | # that process (while maintaining the separation between arguments), and wrap 232 | # the whole thing up as a single "set" statement. 233 | # 234 | # This will of course break if any of these variables contains a newline or 235 | # an unmatched quote. 236 | # 237 | 238 | eval "set -- $( 239 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 240 | xargs -n1 | 241 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 242 | tr '\n' ' ' 243 | )" '"$@"' 244 | 245 | exec "$JAVACMD" "$@" 246 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /screenshots/1.0.1/Screenshot_2022-08-24-02-51-48-048_io.github.yamin8000.owl.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yamin8000/Owl2/abf80da26053409758c41c5d2b4e88116d42fba4/screenshots/1.0.1/Screenshot_2022-08-24-02-51-48-048_io.github.yamin8000.owl.jpg -------------------------------------------------------------------------------- /screenshots/1.0.4/photo_2022-09-26_09-12-54.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yamin8000/Owl2/abf80da26053409758c41c5d2b4e88116d42fba4/screenshots/1.0.4/photo_2022-09-26_09-12-54.png -------------------------------------------------------------------------------- /screenshots/1.0.4/photo_2022-09-26_09-12-55 (2).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yamin8000/Owl2/abf80da26053409758c41c5d2b4e88116d42fba4/screenshots/1.0.4/photo_2022-09-26_09-12-55 (2).png -------------------------------------------------------------------------------- /screenshots/1.0.7/photo_2022-11-10_22-27-32.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yamin8000/Owl2/abf80da26053409758c41c5d2b4e88116d42fba4/screenshots/1.0.7/photo_2022-11-10_22-27-32.jpg -------------------------------------------------------------------------------- /screenshots/1.0.7/photo_2022-11-10_22-27-33.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yamin8000/Owl2/abf80da26053409758c41c5d2b4e88116d42fba4/screenshots/1.0.7/photo_2022-11-10_22-27-33.jpg -------------------------------------------------------------------------------- /screenshots/1.0.7/photo_2022-11-10_22-27-34.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yamin8000/Owl2/abf80da26053409758c41c5d2b4e88116d42fba4/screenshots/1.0.7/photo_2022-11-10_22-27-34.jpg -------------------------------------------------------------------------------- /screenshots/1.0.7/photo_2022-11-10_22-27-36.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yamin8000/Owl2/abf80da26053409758c41c5d2b4e88116d42fba4/screenshots/1.0.7/photo_2022-11-10_22-27-36.jpg -------------------------------------------------------------------------------- /screenshots/1.0.7/photo_2022-11-10_22-27-37.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yamin8000/Owl2/abf80da26053409758c41c5d2b4e88116d42fba4/screenshots/1.0.7/photo_2022-11-10_22-27-37.jpg -------------------------------------------------------------------------------- /screenshots/1.0.7/photo_2022-11-10_22-27-38.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yamin8000/Owl2/abf80da26053409758c41c5d2b4e88116d42fba4/screenshots/1.0.7/photo_2022-11-10_22-27-38.jpg -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Owl: an android app for Owlbot Dictionary API 3 | * settings.gradle Created by Yamin Siahmargooei at 2022/6/16 4 | * This file is part of Owl. 5 | * Copyright (C) 2022 Yamin Siahmargooei 6 | * 7 | * Owl is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * Owl is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Owl. If not, see . 19 | */ 20 | 21 | pluginManagement { 22 | repositories { 23 | gradlePluginPortal() 24 | google() 25 | mavenCentral() 26 | } 27 | } 28 | dependencyResolutionManagement { 29 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 30 | repositories { 31 | google() 32 | mavenCentral() 33 | } 34 | } 35 | rootProject.name = "Owl2" 36 | include ':app' 37 | --------------------------------------------------------------------------------