├── .idea ├── .name ├── .gitignore ├── codeStyles │ ├── codeStyleConfig.xml │ └── Project.xml ├── compiler.xml ├── kotlinc.xml ├── migrations.xml ├── deploymentTargetSelector.xml ├── misc.xml ├── gradle.xml └── appInsightsSettings.xml ├── app ├── .gitignore ├── src │ └── main │ │ ├── res │ │ ├── drawable │ │ │ ├── logo.png │ │ │ ├── splash.png │ │ │ ├── bg_trend.xml │ │ │ ├── bg.xml │ │ │ ├── divider.xml │ │ │ ├── bg_blue.xml │ │ │ ├── select_home.xml │ │ │ ├── select_profile.xml │ │ │ ├── select_video.xml │ │ │ ├── select_category.xml │ │ │ ├── bg_shadow_dark.xml │ │ │ ├── ic_line.xml │ │ │ ├── bg_shadow.xml │ │ │ ├── switch_icon.xml │ │ │ ├── bookmark.xml │ │ │ ├── video_bold.xml │ │ │ ├── user_bold.xml │ │ │ ├── arrow_left.xml │ │ │ ├── arrow_right.xml │ │ │ ├── ic_mon.xml │ │ │ ├── home_bold.xml │ │ │ ├── placeholder.xml │ │ │ ├── heart.xml │ │ │ ├── user.xml │ │ │ ├── ic_dark_mode.xml │ │ │ ├── bookmark_out.xml │ │ │ ├── home.xml │ │ │ ├── category.xml │ │ │ ├── video.xml │ │ │ ├── ic_share.xml │ │ │ ├── search.xml │ │ │ ├── category_out.xml │ │ │ ├── ic_sun.xml │ │ │ ├── ic_comment.xml │ │ │ ├── ic_launcher_foreground.xml │ │ │ ├── notification.xml │ │ │ ├── ic_help_center.xml │ │ │ ├── brand.xml │ │ │ └── ic_launcher_background.xml │ │ ├── font │ │ │ └── is_mobile_bold.ttf │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ ├── anim │ │ │ └── flash_leave_now.xml │ │ ├── color │ │ │ └── item_bottom_nav_tint.xml │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ ├── values │ │ │ ├── attrs.xml │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── themes.xml │ │ ├── xml │ │ │ ├── backup_rules.xml │ │ │ ├── data_extraction_rules.xml │ │ │ └── fragment_splash_scene.xml │ │ ├── menu │ │ │ └── menu_home.xml │ │ ├── layout │ │ │ ├── item_categories.xml │ │ │ ├── internet_error.xml │ │ │ ├── activity_main.xml │ │ │ ├── item_hot.xml │ │ │ ├── fragment_splash.xml │ │ │ ├── fragment_categories.xml │ │ │ ├── fragment_video.xml │ │ │ ├── item_today.xml │ │ │ ├── item_suggestions.xml │ │ │ ├── fragment_profile.xml │ │ │ └── profile_user_order.xml │ │ ├── navigation │ │ │ └── nav_main.xml │ │ └── values-night │ │ │ └── themes.xml │ │ ├── assets │ │ └── fonts │ │ │ ├── is_bold.ttf │ │ │ ├── is_light.ttf │ │ │ ├── tanha_fd.ttf │ │ │ └── is_mobile_bold.ttf │ │ ├── java │ │ └── com │ │ │ └── mehdisekoba │ │ │ └── digiato │ │ │ ├── data │ │ │ ├── repository │ │ │ │ ├── CategoryRepository.kt │ │ │ │ ├── MobileRepository.kt │ │ │ │ └── HomeRepository.kt │ │ │ ├── model │ │ │ │ ├── ErrorResponse.kt │ │ │ │ ├── category │ │ │ │ │ └── ResponseCategory.kt │ │ │ │ ├── home │ │ │ │ │ ├── ResponseTodayHot.kt │ │ │ │ │ ├── ResponseSuggestions.kt │ │ │ │ │ └── ResponseToday.kt │ │ │ │ └── video │ │ │ │ │ └── ResponseMobileVideo.kt │ │ │ ├── database │ │ │ │ ├── entity │ │ │ │ │ ├── NewsTodayEntity.kt │ │ │ │ │ ├── NewsHotEntity.kt │ │ │ │ │ ├── MobileNewsEntity.kt │ │ │ │ │ └── NewsSuggestionsEntity.kt │ │ │ │ ├── NewsAppDatabase.kt │ │ │ │ ├── NewsAppTypeConverter.kt │ │ │ │ └── NewsAppDao.kt │ │ │ ├── source │ │ │ │ ├── RemoteDataSource.kt │ │ │ │ └── LocalDataSource.kt │ │ │ ├── network │ │ │ │ └── ApiServices.kt │ │ │ └── stored │ │ │ │ └── ThemeManager.kt │ │ │ ├── utils │ │ │ ├── network │ │ │ │ ├── NetworkRequest.kt │ │ │ │ ├── NetworkResponse.kt │ │ │ │ └── NetworkChecker.kt │ │ │ ├── base │ │ │ │ ├── BaseActivity.kt │ │ │ │ ├── BaseDiffUtils.kt │ │ │ │ └── BaseFragment.kt │ │ │ ├── views │ │ │ │ └── lightprogress │ │ │ │ │ └── CustomSpringInterpolator.kt │ │ │ ├── Constants.kt │ │ │ ├── MyApp.kt │ │ │ ├── di │ │ │ │ ├── DatabaseModule.kt │ │ │ │ ├── CheckConnection.kt │ │ │ │ └── NetworkModule.kt │ │ │ ├── extensions │ │ │ │ └── Extensions.kt │ │ │ └── other │ │ │ │ └── CustomDividerItemDecoration.kt │ │ │ ├── viewmodel │ │ │ ├── CategoryViewModel.kt │ │ │ ├── MobileViewModel.kt │ │ │ └── HomeViewModel.kt │ │ │ └── ui │ │ │ ├── home │ │ │ └── adapters │ │ │ │ ├── AdapterHotToday.kt │ │ │ │ ├── AdapterSuggestions.kt │ │ │ │ └── AdapterToday.kt │ │ │ ├── profile │ │ │ └── ProfileFragment.kt │ │ │ ├── categories │ │ │ ├── AdapterCategories.kt │ │ │ └── CategoriesFragment.kt │ │ │ ├── splash │ │ │ └── SplashFragment.kt │ │ │ ├── MainActivity.kt │ │ │ └── video │ │ │ ├── AdapterMobile.kt │ │ │ └── VideoFragment.kt │ │ └── AndroidManifest.xml ├── proguard-rules.pro └── build.gradle.kts ├── art ├── photo_2024-07-28_21-16-22.jpg ├── photo_10_2024-07-28_21-16-49.jpg ├── photo_1_2024-07-28_21-16-49.jpg ├── photo_2_2024-07-28_21-16-49.jpg ├── photo_3_2024-07-28_21-16-49.jpg ├── photo_4_2024-07-28_21-16-49.jpg ├── photo_5_2024-07-28_21-16-49.jpg ├── photo_6_2024-07-28_21-16-49.jpg ├── photo_7_2024-07-28_21-16-49.jpg ├── photo_8_2024-07-28_21-16-49.jpg ├── photo_9_2024-07-28_21-16-49.jpg ├── Screenshot_2024-07-29-11-40-21-505_com.mehdisekoba.digiato.jpg └── Screenshot_2024-07-29-11-41-41-469_com.mehdisekoba.digiato.jpg ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── libs.versions.toml ├── .gitignore ├── settings.gradle.kts ├── gradle.properties ├── gradlew.bat ├── README.md └── gradlew /.idea/.name: -------------------------------------------------------------------------------- 1 | Digiato -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MehdiSekoba/Digiato/HEAD/app/src/main/res/drawable/logo.png -------------------------------------------------------------------------------- /art/photo_2024-07-28_21-16-22.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MehdiSekoba/Digiato/HEAD/art/photo_2024-07-28_21-16-22.jpg -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MehdiSekoba/Digiato/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/drawable/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MehdiSekoba/Digiato/HEAD/app/src/main/res/drawable/splash.png -------------------------------------------------------------------------------- /art/photo_10_2024-07-28_21-16-49.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MehdiSekoba/Digiato/HEAD/art/photo_10_2024-07-28_21-16-49.jpg -------------------------------------------------------------------------------- /art/photo_1_2024-07-28_21-16-49.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MehdiSekoba/Digiato/HEAD/art/photo_1_2024-07-28_21-16-49.jpg -------------------------------------------------------------------------------- /art/photo_2_2024-07-28_21-16-49.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MehdiSekoba/Digiato/HEAD/art/photo_2_2024-07-28_21-16-49.jpg -------------------------------------------------------------------------------- /art/photo_3_2024-07-28_21-16-49.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MehdiSekoba/Digiato/HEAD/art/photo_3_2024-07-28_21-16-49.jpg -------------------------------------------------------------------------------- /art/photo_4_2024-07-28_21-16-49.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MehdiSekoba/Digiato/HEAD/art/photo_4_2024-07-28_21-16-49.jpg -------------------------------------------------------------------------------- /art/photo_5_2024-07-28_21-16-49.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MehdiSekoba/Digiato/HEAD/art/photo_5_2024-07-28_21-16-49.jpg -------------------------------------------------------------------------------- /art/photo_6_2024-07-28_21-16-49.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MehdiSekoba/Digiato/HEAD/art/photo_6_2024-07-28_21-16-49.jpg -------------------------------------------------------------------------------- /art/photo_7_2024-07-28_21-16-49.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MehdiSekoba/Digiato/HEAD/art/photo_7_2024-07-28_21-16-49.jpg -------------------------------------------------------------------------------- /art/photo_8_2024-07-28_21-16-49.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MehdiSekoba/Digiato/HEAD/art/photo_8_2024-07-28_21-16-49.jpg -------------------------------------------------------------------------------- /art/photo_9_2024-07-28_21-16-49.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MehdiSekoba/Digiato/HEAD/art/photo_9_2024-07-28_21-16-49.jpg -------------------------------------------------------------------------------- /app/src/main/assets/fonts/is_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MehdiSekoba/Digiato/HEAD/app/src/main/assets/fonts/is_bold.ttf -------------------------------------------------------------------------------- /app/src/main/assets/fonts/is_light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MehdiSekoba/Digiato/HEAD/app/src/main/assets/fonts/is_light.ttf -------------------------------------------------------------------------------- /app/src/main/assets/fonts/tanha_fd.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MehdiSekoba/Digiato/HEAD/app/src/main/assets/fonts/tanha_fd.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/is_mobile_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MehdiSekoba/Digiato/HEAD/app/src/main/res/font/is_mobile_bold.ttf -------------------------------------------------------------------------------- /app/src/main/assets/fonts/is_mobile_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MehdiSekoba/Digiato/HEAD/app/src/main/assets/fonts/is_mobile_bold.ttf -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MehdiSekoba/Digiato/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MehdiSekoba/Digiato/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MehdiSekoba/Digiato/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MehdiSekoba/Digiato/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MehdiSekoba/Digiato/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MehdiSekoba/Digiato/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MehdiSekoba/Digiato/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MehdiSekoba/Digiato/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MehdiSekoba/Digiato/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MehdiSekoba/Digiato/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /art/Screenshot_2024-07-29-11-40-21-505_com.mehdisekoba.digiato.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MehdiSekoba/Digiato/HEAD/art/Screenshot_2024-07-29-11-40-21-505_com.mehdisekoba.digiato.jpg -------------------------------------------------------------------------------- /art/Screenshot_2024-07-29-11-41-41-469_com.mehdisekoba.digiato.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MehdiSekoba/Digiato/HEAD/art/Screenshot_2024-07-29-11-41-41-469_com.mehdisekoba.digiato.jpg -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_trend.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/divider.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_blue.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Jul 21 10:00:15 IRST 2024 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /.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/migrations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/anim/flash_leave_now.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/color/item_bottom_nav_tint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/select_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/select_profile.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/select_video.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/select_category.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/data/repository/CategoryRepository.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.data.repository 2 | 3 | import com.mehdisekoba.digiato.data.network.ApiServices 4 | import javax.inject.Inject 5 | 6 | class CategoryRepository @Inject constructor(private val api: ApiServices) { 7 | suspend fun getCategoryList() = api.getCategoryList() 8 | } -------------------------------------------------------------------------------- /.idea/deploymentTargetSelector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_shadow_dark.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_line.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/data/model/ErrorResponse.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.data.model 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class ErrorResponse( 6 | @SerializedName("errors") 7 | val errors: Map>?, 8 | @SerializedName("message") 9 | val message: String? // The given data was invalid. 10 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/utils/network/NetworkRequest.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.utils.network 2 | 3 | sealed class NetworkRequest(val data: T? = null, val error: String? = null) { 4 | class Loading : NetworkRequest() 5 | class Success(data: T) : NetworkRequest(data) 6 | class Error(message: String) : NetworkRequest(error = message) 7 | } -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_shadow.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/data/repository/MobileRepository.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.data.repository 2 | 3 | import com.mehdisekoba.digiato.data.source.LocalDataSource 4 | import com.mehdisekoba.digiato.data.source.RemoteDataSource 5 | import javax.inject.Inject 6 | 7 | class MobileRepository @Inject constructor(remoteDataSource: RemoteDataSource,localDataSource: LocalDataSource) { 8 | val remote = remoteDataSource 9 | val local = localDataSource 10 | 11 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/utils/base/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.utils.base 2 | 3 | import android.content.Context 4 | import androidx.appcompat.app.AppCompatActivity 5 | import io.github.inflationx.viewpump.ViewPumpContextWrapper 6 | 7 | open class BaseActivity:AppCompatActivity() { 8 | // Calligraphy 9 | override fun attachBaseContext(newBase: Context) { 10 | super.attachBaseContext(ViewPumpContextWrapper.wrap(newBase)) 11 | } 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/data/database/entity/NewsTodayEntity.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.data.database.entity 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | import com.mehdisekoba.digiato.data.model.home.ResponseToday 6 | import com.mehdisekoba.digiato.utils.TODAY_TABLE_NAME 7 | 8 | @Entity(tableName = TODAY_TABLE_NAME) 9 | data class NewsTodayEntity ( 10 | @PrimaryKey(autoGenerate = false) 11 | var id: Int = 2, 12 | var result: ResponseToday, 13 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/data/database/entity/NewsHotEntity.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.data.database.entity 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | import com.mehdisekoba.digiato.data.model.home.ResponseTodayHot 6 | import com.mehdisekoba.digiato.utils.TODAY_HOT_TABLE_NAME 7 | 8 | @Entity(tableName = TODAY_HOT_TABLE_NAME) 9 | data class NewsHotEntity ( 10 | @PrimaryKey(autoGenerate = false) 11 | var id: Int = 0, 12 | var result: ResponseTodayHot, 13 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/data/database/entity/MobileNewsEntity.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.data.database.entity 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | import com.mehdisekoba.digiato.data.model.video.ResponseMobileVideo 6 | import com.mehdisekoba.digiato.utils.MOBILE_TABLE_NAME 7 | 8 | @Entity(tableName = MOBILE_TABLE_NAME) 9 | data class MobileNewsEntity ( 10 | @PrimaryKey(autoGenerate = false) 11 | var id: Int = 3, 12 | var result: ResponseMobileVideo, 13 | ) -------------------------------------------------------------------------------- /app/src/main/res/drawable/switch_icon.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/data/database/entity/NewsSuggestionsEntity.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.data.database.entity 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | import com.mehdisekoba.digiato.data.model.home.ResponseSuggestions 6 | import com.mehdisekoba.digiato.utils.SUGGESTIONS_TABLE_NAME 7 | 8 | @Entity(tableName = SUGGESTIONS_TABLE_NAME) 9 | data class NewsSuggestionsEntity ( 10 | @PrimaryKey(autoGenerate = false) 11 | var id: Int = 1, 12 | var result: ResponseSuggestions, 13 | ) -------------------------------------------------------------------------------- /app/src/main/res/drawable/bookmark.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/data/source/RemoteDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.data.source 2 | 3 | import com.mehdisekoba.digiato.data.network.ApiServices 4 | import javax.inject.Inject 5 | 6 | class RemoteDataSource 7 | @Inject 8 | constructor(private val api: ApiServices) { 9 | suspend fun getSuggestionsNewsList() = api.getSuggestionsNewsList() 10 | 11 | suspend fun getTodayNewsList() = api.getTodayNewsList() 12 | suspend fun getToadyHotList() = api.getToadyHotList() 13 | suspend fun getMobileList() = api.getMobileList() 14 | 15 | 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/video_bold.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/data/repository/HomeRepository.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.data.repository 2 | 3 | import com.mehdisekoba.digiato.data.source.LocalDataSource 4 | import com.mehdisekoba.digiato.data.source.RemoteDataSource 5 | import dagger.hilt.android.scopes.ActivityRetainedScoped 6 | import javax.inject.Inject 7 | 8 | @ActivityRetainedScoped 9 | class HomeRepository 10 | @Inject 11 | constructor(remoteDataSource: RemoteDataSource, localDataSource: LocalDataSource) { 12 | val remote = remoteDataSource 13 | val local = localDataSource 14 | 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/data/model/category/ResponseCategory.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.data.model.category 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | class ResponseCategory : ArrayList(){ 7 | data class ResponseCategoryItem( 8 | @SerializedName("category") 9 | val category: String?, // Technology 10 | @SerializedName("image") 11 | val image: String?, // https://news.mehdisekoba.ir/images/Technology.png 12 | @SerializedName("title") 13 | val title: String? // تکنولوژی 14 | ) 15 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/user_bold.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/arrow_left.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/utils/views/lightprogress/CustomSpringInterpolator.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.utils.views.lightprogress 2 | 3 | import android.view.animation.Interpolator 4 | import kotlin.math.pow 5 | import kotlin.math.sin 6 | 7 | /** 8 | * Created by https://github.com/geetgobindsingh 9 | * https://github.com/geetgobindsingh/AndroidAnimationInterpolator 10 | */ 11 | class CustomSpringInterpolator(private var factor: Float) : Interpolator { 12 | 13 | override fun getInterpolation(input: Float): Float { 14 | return (2.0.pow(-6.5 * input) * sin(2 * Math.PI * (input - factor / 4) / factor) + 1).toFloat() 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/arrow_right.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_mon.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google { 4 | content { 5 | includeGroupByRegex("com\\.android.*") 6 | includeGroupByRegex("com\\.google.*") 7 | includeGroupByRegex("androidx.*") 8 | } 9 | } 10 | mavenCentral() 11 | gradlePluginPortal() 12 | } 13 | } 14 | dependencyResolutionManagement { 15 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 16 | repositories { 17 | google() 18 | mavenCentral() 19 | maven { url = uri("https://jitpack.io") } 20 | 21 | } 22 | } 23 | 24 | rootProject.name = "Digiato" 25 | include(":app") 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/data/model/home/ResponseTodayHot.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.data.model.home 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | class ResponseTodayHot : ArrayList(){ 7 | data class ResponseTodayHotItem( 8 | @SerializedName("date") 9 | val date: String?, // 20 ساعت قبل 10 | @SerializedName("image") 11 | val image: String?, // https://static.digiato.com/digiato/2024/07/James_Gathany_courtesy_of_Centers_for_Disease.width-2500-80x80.jpg 12 | @SerializedName("title") 13 | val title: String? // همه‌چیز درباره پشه آئدس؛ تشخیص، علائم و درمان تب دنگی 14 | ) 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/utils/Constants.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.utils 2 | 3 | // Server 4 | const val BASE_URL = "https://news.mehdisekoba.ir/" 5 | const val CONNECTION_TIME = 60L 6 | const val PING_INTERVAL = 3L 7 | 8 | // Injection named 9 | const val NAMED_PING = "named_ping" 10 | 11 | // Other 12 | const val SETTINGS_DATA = "settings_data" 13 | const val DARK_STATUS = "dark_status" 14 | 15 | //Database 16 | const val TODAY_TABLE_NAME = "today_table_name" 17 | const val SUGGESTIONS_TABLE_NAME = "suggestions_table_name" 18 | const val TODAY_HOT_TABLE_NAME = "today_hot_table_name" 19 | const val MOBILE_TABLE_NAME = "mobile_table_name" 20 | 21 | const val DATABASE_NAME = "database_name" 22 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/home_bold.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 11 | 15 | 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/data/database/NewsAppDatabase.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.data.database 2 | 3 | import androidx.room.Database 4 | import androidx.room.RoomDatabase 5 | import androidx.room.TypeConverters 6 | import com.mehdisekoba.digiato.data.database.entity.MobileNewsEntity 7 | import com.mehdisekoba.digiato.data.database.entity.NewsHotEntity 8 | import com.mehdisekoba.digiato.data.database.entity.NewsSuggestionsEntity 9 | import com.mehdisekoba.digiato.data.database.entity.NewsTodayEntity 10 | 11 | @Database(entities = [NewsHotEntity::class, NewsSuggestionsEntity::class, NewsTodayEntity::class, MobileNewsEntity::class], version = 3, exportSchema = false) 12 | @TypeConverters(NewsAppTypeConverter::class) 13 | abstract class NewsAppDatabase :RoomDatabase(){ 14 | abstract fun newsAppDao(): NewsAppDao 15 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/placeholder.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 14 | 19 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/utils/base/BaseDiffUtils.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.utils.base 2 | 3 | import androidx.recyclerview.widget.DiffUtil 4 | 5 | class BaseDiffUtils(private val oldList: List, private val newList: List):DiffUtil.Callback() { 6 | override fun getOldListSize(): Int { 7 | return oldList.size 8 | } 9 | 10 | override fun getNewListSize(): Int { 11 | return newList.size 12 | } 13 | 14 | override fun areItemsTheSame( 15 | oldListPosition: Int, 16 | newItemPosition: Int, 17 | ): Boolean { 18 | return oldList[oldListPosition] === newList[newItemPosition] 19 | } 20 | 21 | override fun areContentsTheSame( 22 | oldListPosition: Int, 23 | newItemPosition: Int, 24 | ): Boolean { 25 | return oldList[oldListPosition] === newList[newItemPosition] 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/heart.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/user.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/data/model/home/ResponseSuggestions.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.data.model.home 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | class ResponseSuggestions : ArrayList(){ 7 | data class ResponseSuggestionsItem( 8 | @SerializedName("author") 9 | val author: String?, // آزاد کبیری 10 | @SerializedName("category") 11 | val category: String?, // میان رشته ای 12 | @SerializedName("date") 13 | val date: String?, // 24 دقیقه قبل 14 | @SerializedName("image") 15 | val image: String?, // https://static.digiato.com/digiato/2024/07/Sam-Altmans-giant-basic-income-study-is-out-Heres-what-388x462.jpeg 16 | @SerializedName("title") 17 | val title: String? // پژوهش سم آلتمن نشان داد: چه می‌شد اگر ماهانه 1000 دلار رایگان به مردم پول داده می‌شد؟ 18 | ) 19 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_dark_mode.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/utils/MyApp.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.utils 2 | 3 | import android.app.Application 4 | import com.jakewharton.threetenabp.AndroidThreeTen 5 | import dagger.hilt.android.HiltAndroidApp 6 | import io.github.inflationx.calligraphy3.CalligraphyConfig 7 | import io.github.inflationx.calligraphy3.CalligraphyInterceptor 8 | import io.github.inflationx.viewpump.ViewPump 9 | 10 | @HiltAndroidApp 11 | class MyApp : Application() { 12 | override fun onCreate() { 13 | super.onCreate() 14 | //date 15 | AndroidThreeTen.init(this) 16 | //Calligraphy 17 | ViewPump.init( 18 | ViewPump.builder().addInterceptor( 19 | CalligraphyInterceptor( 20 | CalligraphyConfig.Builder() 21 | .setDefaultFontPath("fonts/is_bold.ttf") 22 | .build() 23 | ) 24 | ).build() 25 | ) 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/bookmark_out.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 9 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/appInsightsSettings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/utils/di/DatabaseModule.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.utils.di 2 | 3 | import android.content.Context 4 | import androidx.room.Room 5 | import com.mehdisekoba.digiato.data.database.NewsAppDatabase 6 | import com.mehdisekoba.digiato.utils.DATABASE_NAME 7 | import dagger.Module 8 | import dagger.Provides 9 | import dagger.hilt.InstallIn 10 | import dagger.hilt.android.qualifiers.ApplicationContext 11 | import dagger.hilt.components.SingletonComponent 12 | import javax.inject.Singleton 13 | 14 | @Module 15 | @InstallIn(SingletonComponent::class) 16 | object DatabaseModule { 17 | @Provides 18 | @Singleton 19 | fun provideDatabase(@ApplicationContext context: Context) =Room.databaseBuilder( 20 | context, 21 | NewsAppDatabase::class.java, 22 | DATABASE_NAME 23 | ).allowMainThreadQueries() 24 | .fallbackToDestructiveMigration() 25 | .build() 26 | 27 | @Provides 28 | @Singleton 29 | fun provideDao(database: NewsAppDatabase) = database.newsAppDao() 30 | 31 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/data/network/ApiServices.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.data.network 2 | 3 | import com.mehdisekoba.digiato.data.model.category.ResponseCategory 4 | import com.mehdisekoba.digiato.data.model.home.ResponseSuggestions 5 | import com.mehdisekoba.digiato.data.model.home.ResponseToday 6 | import com.mehdisekoba.digiato.data.model.home.ResponseTodayHot 7 | import com.mehdisekoba.digiato.data.model.video.ResponseMobileVideo 8 | import retrofit2.Response 9 | import retrofit2.http.GET 10 | 11 | interface ApiServices { 12 | @GET("suggestions.php") 13 | suspend fun getSuggestionsNewsList(): Response 14 | 15 | @GET("todayNews.php") 16 | suspend fun getTodayNewsList(): Response 17 | 18 | @GET("category.php") 19 | suspend fun getCategoryList(): Response 20 | 21 | @GET("mobile.php") 22 | suspend fun getMobileList(): Response 23 | 24 | @GET("TodayHot.php") 25 | suspend fun getToadyHotList(): Response 26 | 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/home.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/category.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/data/model/video/ResponseMobileVideo.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.data.model.video 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | class ResponseMobileVideo : ArrayList(){ 7 | data class ResponseMobileVideoItem( 8 | @SerializedName("author") 9 | val author: String?, // مهرانا عیسی‌پور 10 | @SerializedName("author_avatar") 11 | val authorAvatar: String?, // https://digiato.com/wp-content/uploads/avatars/280902-1712054156-32x32.jpg 12 | @SerializedName("category") 13 | val category: String?, // نقد و بررسی‌ 14 | @SerializedName("date") 15 | val date: String?, // 5 روز قبل 16 | @SerializedName("description") 17 | val description: String?, // به‌نظر می‌رسد سامسونگ گلکسی Z Fold 6 یک گوشی تاشو جذاب با قابلیت‌های جدید هوش مصنوعی در یک طراحی سبک‌تر و بادوام‌تر است 18 | @SerializedName("image") 19 | val image: String?, // https://static.digiato.com/digiato/2024/07/zfold-6-review-Digiato-01-791x482.jpg.webp 20 | @SerializedName("title") 21 | val title: String? // بررسی سامسونگ گلکسی زدفولد 6: دوست‌داشتنی و قدرتمند 22 | ) 23 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/video.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/data/model/home/ResponseToday.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.data.model.home 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | class ResponseToday : ArrayList(){ 7 | data class ResponseTodayItem( 8 | @SerializedName("author") 9 | val author: String?, // حمیدرضا علیا 10 | @SerializedName("author_avatar") 11 | val authorAvatar: String?, // https://digiato.com/wp-content/uploads/avatars/292874-1707111771-32x32.jpg 12 | @SerializedName("category") 13 | val category: String?, // کامپیوتر و سخت افزار 14 | @SerializedName("date") 15 | val date: String?, // 7 ساعت قبل 16 | @SerializedName("description") 17 | val description: String?, // گزارشی تازه می‌گوید انویدیا به احتمال زیاد نسل بعدی کارت‌های گرافیکی گیمینگ RTX 50 Blackwell را تا پایان سال جاری میلادی معرفی نمی‌کند. 18 | @SerializedName("image") 19 | val image: String?, // https://static.digiato.com/digiato/2024/07/hAcZNwUpeQQAsWfS7LZ4DC-1200-80-791x482.jpg.webp 20 | @SerializedName("title") 21 | val title: String? // انویدیا احتمالاً پردازشگرهای گرافیکی RTX 50 Blackwell را در CES 2025 معرفی می‌کند 22 | ) 23 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_share.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/data/source/LocalDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.data.source 2 | 3 | import com.mehdisekoba.digiato.data.database.NewsAppDao 4 | import com.mehdisekoba.digiato.data.database.entity.MobileNewsEntity 5 | import com.mehdisekoba.digiato.data.database.entity.NewsHotEntity 6 | import com.mehdisekoba.digiato.data.database.entity.NewsSuggestionsEntity 7 | import com.mehdisekoba.digiato.data.database.entity.NewsTodayEntity 8 | import javax.inject.Inject 9 | 10 | class LocalDataSource @Inject constructor(private val dao: NewsAppDao) { 11 | //Home 12 | suspend fun insertHotNews(newsHotEntity: NewsHotEntity) = dao.insertHotNews(newsHotEntity) 13 | fun loadHotNews() = dao.loadHotNews() 14 | 15 | //Suggestions 16 | suspend fun insertSuggestionsNews(suggestionsEntity: NewsSuggestionsEntity) = 17 | dao.insertSuggestionsNews(suggestionsEntity) 18 | 19 | fun loadSuggestionsNews() = dao.loadSuggestionsNews() 20 | 21 | //Today 22 | suspend fun insertTodayNews(todayEntity: NewsTodayEntity) = dao.insertTodayNews(todayEntity) 23 | fun loadTodayNews() = dao.loadTodayNews() 24 | 25 | //mobile 26 | suspend fun insertMobileNews(mobileEntity: MobileNewsEntity) = 27 | dao.insertMobileNews(mobileEntity) 28 | 29 | fun loadMobileNews() = dao.loadMobileNews() 30 | 31 | 32 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/utils/extensions/Extensions.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.utils.extensions 2 | 3 | import android.widget.ImageView 4 | import androidx.lifecycle.LifecycleOwner 5 | import androidx.lifecycle.LiveData 6 | import androidx.lifecycle.Observer 7 | import coil.load 8 | import coil.request.CachePolicy 9 | import com.mehdisekoba.digiato.R 10 | import com.mehdisekoba.digiato.utils.other.TimeUtils 11 | 12 | 13 | fun ImageView.loadImage(url: String) { 14 | this.load(url) { 15 | crossfade(true) 16 | crossfade(500) 17 | diskCachePolicy(CachePolicy.ENABLED) 18 | error(R.drawable.placeholder) 19 | } 20 | } 21 | 22 | fun String.convertToDateNameFa(): String { 23 | val dateSplit = this.split("-") 24 | val year = dateSplit[0].toInt() 25 | val month = dateSplit[1].toInt() 26 | val day = dateSplit[2].toInt() 27 | val timeUtils = TimeUtils(year, month, day) 28 | val iranianMonth = timeUtils.iranianMonthName 29 | val iranianDay = timeUtils.iranianDay 30 | return "$iranianDay _ $iranianMonth" 31 | } 32 | 33 | fun LiveData.onceObserve(owner: LifecycleOwner, observe: Observer) { 34 | observe(owner, object : Observer { 35 | override fun onChanged(value: T) { 36 | removeObserver(this) 37 | observe.onChanged(value) 38 | } 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/utils/network/NetworkResponse.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.utils.network 2 | 3 | import com.google.gson.Gson 4 | import com.mehdisekoba.digiato.data.model.ErrorResponse 5 | import retrofit2.Response 6 | 7 | open class NetworkResponse (private val response: Response) { 8 | 9 | open fun generateResponse(): NetworkRequest { 10 | return when { 11 | response.code() == 401 -> NetworkRequest.Error("You are not authorized") 12 | response.code() == 422 -> { 13 | var errorMessage = "" 14 | if (response.errorBody() != null) { 15 | val errorResponse = Gson().fromJson(response.errorBody()?.charStream(), ErrorResponse::class.java) 16 | //val message = errorResponse.message 17 | val errors = errorResponse.errors 18 | errors?.forEach { (_, fieldError) -> 19 | errorMessage = fieldError.joinToString() 20 | } 21 | } 22 | NetworkRequest.Error(errorMessage) 23 | } 24 | 25 | response.code() == 500 -> NetworkRequest.Error("Try again") 26 | response.isSuccessful -> NetworkRequest.Success(response.body()!!) 27 | else -> NetworkRequest.Error(response.message()) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/search.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_categories.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 16 | 17 | 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/utils/di/CheckConnection.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.utils.di 2 | 3 | import android.content.Context 4 | import android.net.ConnectivityManager 5 | import android.net.NetworkCapabilities 6 | import android.net.NetworkRequest 7 | import android.os.Build 8 | import dagger.Module 9 | import dagger.Provides 10 | import dagger.hilt.InstallIn 11 | import dagger.hilt.android.qualifiers.ApplicationContext 12 | import dagger.hilt.components.SingletonComponent 13 | import javax.inject.Singleton 14 | 15 | @Module 16 | @InstallIn(SingletonComponent::class) 17 | class CheckConnection { 18 | @Provides 19 | @Singleton 20 | fun provideCM(@ApplicationContext context: Context) = 21 | context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 22 | 23 | @Provides 24 | @Singleton 25 | fun provideNR(): NetworkRequest = NetworkRequest.Builder().apply { 26 | addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) 27 | addTransportType(NetworkCapabilities.TRANSPORT_WIFI) 28 | addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) 29 | // Android M 30 | addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) 31 | // Android P 32 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { 33 | addCapability(NetworkCapabilities.NET_CAPABILITY_FOREGROUND) 34 | } 35 | }.build() 36 | } -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FF000000 4 | #FFFFFFFF 5 | 6 | #182172 7 | #32373c 8 | #abb8c3 9 | #7bdcb5 10 | #00d084 11 | #8ed1fc 12 | #0693e3 13 | #555555 14 | #ff6900 15 | #484848 16 | @android:color/white 17 | #1F1D36 18 | #801F1D36 19 | 20 | 21 | #888dab 22 | #60bfee 23 | #F3F3F8 24 | #E25B39 25 | #C1D1DA 26 | #93B4D4 27 | 28 | 29 | #14141C 30 | #BFCFDD 31 | #E67950 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/viewmodel/CategoryViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.viewmodel 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | import androidx.lifecycle.viewModelScope 7 | import com.mehdisekoba.digiato.data.model.category.ResponseCategory 8 | import com.mehdisekoba.digiato.data.repository.CategoryRepository 9 | import com.mehdisekoba.digiato.utils.network.NetworkRequest 10 | import com.mehdisekoba.digiato.utils.network.NetworkResponse 11 | import dagger.hilt.android.lifecycle.HiltViewModel 12 | import kotlinx.coroutines.delay 13 | import kotlinx.coroutines.launch 14 | import javax.inject.Inject 15 | 16 | @HiltViewModel 17 | class CategoryViewModel @Inject constructor(private val repository: CategoryRepository) : 18 | ViewModel() { 19 | init { 20 | viewModelScope.launch { 21 | delay(200) 22 | callCategoryApi() 23 | } 24 | } 25 | 26 | //category 27 | private val _categoryData = MutableLiveData>() 28 | val categoryData: LiveData> = _categoryData 29 | private fun callCategoryApi() = viewModelScope.launch { 30 | _categoryData.value = NetworkRequest.Loading() 31 | val response = repository.getCategoryList() 32 | _categoryData.value = NetworkResponse(response).generateResponse() 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/data/stored/ThemeManager.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.data.stored 2 | 3 | import android.content.Context 4 | import androidx.datastore.core.DataStore 5 | import androidx.datastore.preferences.core.Preferences 6 | import androidx.datastore.preferences.core.booleanPreferencesKey 7 | import androidx.datastore.preferences.core.edit 8 | import androidx.datastore.preferences.preferencesDataStore 9 | import com.mehdisekoba.digiato.utils.DARK_STATUS 10 | import com.mehdisekoba.digiato.utils.SETTINGS_DATA 11 | import dagger.hilt.android.qualifiers.ApplicationContext 12 | import kotlinx.coroutines.flow.Flow 13 | import kotlinx.coroutines.flow.map 14 | import javax.inject.Inject 15 | 16 | class ThemeManager @Inject constructor( 17 | @ApplicationContext private val context: Context, 18 | ) { 19 | private val appContext = context.applicationContext 20 | companion object { 21 | private val Context.dataStore: DataStore by preferencesDataStore(SETTINGS_DATA) 22 | private val DARK_STATUS_KEY = booleanPreferencesKey(DARK_STATUS) 23 | } 24 | 25 | 26 | suspend fun saveTheme(isDarkTheme: Boolean) { 27 | appContext.dataStore.edit { 28 | it[DARK_STATUS_KEY] = isDarkTheme 29 | } 30 | } 31 | 32 | 33 | val isDarkTheme: Flow = 34 | appContext.dataStore.data.map { preferences -> 35 | preferences[DARK_STATUS_KEY] ?: false 36 | } 37 | 38 | 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/res/layout/internet_error.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 18 | 19 | 30 | 31 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. For more details, visit 12 | # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true 24 | org.gradle.parallel=true 25 | org.gradle.caching=true 26 | org.gradle.unsafe.isolated-project=true -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/utils/base/BaseFragment.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.utils.base 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import androidx.lifecycle.lifecycleScope 9 | import androidx.viewbinding.ViewBinding 10 | import com.mehdisekoba.digiato.utils.network.NetworkChecker 11 | import kotlinx.coroutines.launch 12 | import javax.inject.Inject 13 | 14 | abstract class BaseFragment : Fragment() { 15 | 16 | //Binding 17 | protected abstract val bindingInflater: (inflater: LayoutInflater) -> T 18 | private var _binding: T? = null 19 | protected val binding: T get() = requireNotNull(_binding) 20 | 21 | @Inject 22 | lateinit var networkChecker: NetworkChecker 23 | 24 | // Other 25 | var isNetworkAvailable = true 26 | override fun onCreateView( 27 | inflater: LayoutInflater, 28 | container: ViewGroup?, 29 | savedInstanceState: Bundle? 30 | ): View? { 31 | _binding = bindingInflater.invoke(inflater) 32 | return binding.root 33 | 34 | } 35 | 36 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 37 | super.onViewCreated(view, savedInstanceState) 38 | //check network 39 | lifecycleScope.launch { 40 | networkChecker.checkNetwork().collect { 41 | isNetworkAvailable = it 42 | } 43 | } 44 | } 45 | 46 | override fun onDestroy() { 47 | super.onDestroy() 48 | _binding = null 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/category_out.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_sun.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_comment.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/notification.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/utils/network/NetworkChecker.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.utils.network 2 | 3 | import android.net.ConnectivityManager 4 | import android.net.Network 5 | import android.net.NetworkCapabilities 6 | import android.net.NetworkRequest 7 | import kotlinx.coroutines.flow.MutableStateFlow 8 | import javax.inject.Inject 9 | 10 | class NetworkChecker @Inject constructor( 11 | private val manager: ConnectivityManager, 12 | private val request: NetworkRequest 13 | ) : ConnectivityManager.NetworkCallback() { 14 | private val isNetworkAvailable = MutableStateFlow(false) 15 | private var capabilities: NetworkCapabilities? = null 16 | 17 | fun checkNetwork(): MutableStateFlow { 18 | // Register 19 | manager.registerNetworkCallback(request, this) 20 | // Active network 21 | val activeNetwork = manager.activeNetwork 22 | if (activeNetwork == null) { 23 | isNetworkAvailable.value = false 24 | return isNetworkAvailable 25 | } 26 | // Capabilities 27 | capabilities = manager.getNetworkCapabilities(activeNetwork) 28 | if (capabilities == null) { 29 | isNetworkAvailable.value = false 30 | return isNetworkAvailable 31 | } 32 | isNetworkAvailable.value = 33 | when { 34 | capabilities!!.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true 35 | capabilities!!.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true 36 | else -> false 37 | } 38 | return isNetworkAvailable 39 | } 40 | 41 | override fun onAvailable(network: Network) { 42 | isNetworkAvailable.value = true 43 | } 44 | 45 | override fun onLost(network: Network) { 46 | isNetworkAvailable.value = false 47 | } 48 | } -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Digiato 3 | 4 | Hello blank fragment 5 | خانه 6 | دسته بندی ها 7 | فیلم ها 8 | پروفایل 9 | Loading 10 | دیجیاتو 11 | رسانه نسل فردا 12 | پر طرفدار 13 | راهنمای خرید گوشی موبایل – مرداد ماه ۱۴۰۳ 14 | پیشنهادهای سردبیر 15 | آزاد کبیری | 1ساعت قبل 16 | نوشته شده توسط 17 | تازه‌های امروز 18 | اتصال اینترنتی خود را چک کنید 19 | موبایل 20 | حساب کاربری 21 | ساخت حساب کاربری جدید 22 | ورود به حساب کاربری 23 | اعلان 24 | تم برنامه 25 | بازخورد 26 | راهنمایی 27 | تم برنامه 28 | شب 29 | روز 30 | داغ‌ترین‌های امروز 31 | اینترنت نداری رفیق ! 32 | 33 | 34 | حالت روز 35 | حالت شب 36 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 22 | 23 | 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/data/database/NewsAppTypeConverter.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.data.database 2 | 3 | import androidx.room.TypeConverter 4 | import com.google.gson.Gson 5 | import com.mehdisekoba.digiato.data.model.home.ResponseSuggestions 6 | import com.mehdisekoba.digiato.data.model.home.ResponseToday 7 | import com.mehdisekoba.digiato.data.model.home.ResponseTodayHot 8 | import com.mehdisekoba.digiato.data.model.video.ResponseMobileVideo 9 | 10 | class NewsAppTypeConverter { 11 | private val gson = Gson() 12 | 13 | //hot news 14 | @TypeConverter 15 | fun hotNewsToJson(result: ResponseTodayHot): String { 16 | return gson.toJson(result) 17 | } 18 | 19 | @TypeConverter 20 | fun stringToHotNews(data: String): ResponseTodayHot { 21 | return gson.fromJson(data, ResponseTodayHot::class.java) 22 | } 23 | 24 | //suggestion 25 | @TypeConverter 26 | fun suggestionNewsToJson(result: ResponseSuggestions): String { 27 | return gson.toJson(result) 28 | } 29 | 30 | @TypeConverter 31 | fun stringToSuggestionNews(data: String): ResponseSuggestions { 32 | return gson.fromJson(data, ResponseSuggestions::class.java) 33 | } 34 | 35 | //today news 36 | @TypeConverter 37 | fun todayNewsToJson(result: ResponseToday): String { 38 | return gson.toJson(result) 39 | } 40 | 41 | @TypeConverter 42 | fun stringToTodayNews(data: String): ResponseToday { 43 | return gson.fromJson(data, ResponseToday::class.java) 44 | } 45 | 46 | //mobile 47 | @TypeConverter 48 | fun mobileNewsToJson(result: ResponseMobileVideo): String { 49 | return gson.toJson(result) 50 | } 51 | 52 | @TypeConverter 53 | fun stringToMobileNews(data: String): ResponseMobileVideo { 54 | return gson.fromJson(data, ResponseMobileVideo::class.java) 55 | } 56 | 57 | 58 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/data/database/NewsAppDao.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.data.database 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.OnConflictStrategy 6 | import androidx.room.Query 7 | import com.mehdisekoba.digiato.data.database.entity.MobileNewsEntity 8 | import com.mehdisekoba.digiato.data.database.entity.NewsHotEntity 9 | import com.mehdisekoba.digiato.data.database.entity.NewsSuggestionsEntity 10 | import com.mehdisekoba.digiato.data.database.entity.NewsTodayEntity 11 | import com.mehdisekoba.digiato.utils.MOBILE_TABLE_NAME 12 | import com.mehdisekoba.digiato.utils.SUGGESTIONS_TABLE_NAME 13 | import com.mehdisekoba.digiato.utils.TODAY_HOT_TABLE_NAME 14 | import com.mehdisekoba.digiato.utils.TODAY_TABLE_NAME 15 | import kotlinx.coroutines.flow.Flow 16 | 17 | @Dao 18 | interface NewsAppDao { 19 | //hot news 20 | @Insert(onConflict = OnConflictStrategy.REPLACE) 21 | suspend fun insertHotNews(newsHotEntity: NewsHotEntity) 22 | 23 | @Query("SELECT * FROM $TODAY_HOT_TABLE_NAME") 24 | fun loadHotNews(): Flow> 25 | 26 | //suggestions 27 | @Insert(onConflict = OnConflictStrategy.REPLACE) 28 | suspend fun insertSuggestionsNews(newsSuggestions: NewsSuggestionsEntity) 29 | 30 | @Query("SELECT * FROM $SUGGESTIONS_TABLE_NAME") 31 | fun loadSuggestionsNews(): Flow> 32 | 33 | //today 34 | @Insert(onConflict = OnConflictStrategy.REPLACE) 35 | suspend fun insertTodayNews(newsTodayEntity: NewsTodayEntity) 36 | 37 | @Query("SELECT * FROM $TODAY_TABLE_NAME") 38 | fun loadTodayNews(): Flow> 39 | 40 | //mobile 41 | @Insert(onConflict = OnConflictStrategy.REPLACE) 42 | suspend fun insertMobileNews(newsEntity: MobileNewsEntity) 43 | 44 | @Query("SELECT * FROM $MOBILE_TABLE_NAME") 45 | fun loadMobileNews(): Flow> 46 | 47 | 48 | } -------------------------------------------------------------------------------- /app/src/main/res/navigation/nav_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 15 | 22 | 23 | 28 | 33 | 38 | 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/ui/home/adapters/AdapterHotToday.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.ui.home.adapters 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.recyclerview.widget.DiffUtil 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.mehdisekoba.digiato.data.model.home.ResponseTodayHot.ResponseTodayHotItem 8 | import com.mehdisekoba.digiato.databinding.ItemHotBinding 9 | import com.mehdisekoba.digiato.utils.base.BaseDiffUtils 10 | import com.mehdisekoba.digiato.utils.extensions.loadImage 11 | import javax.inject.Inject 12 | 13 | class AdapterHotToday @Inject constructor() : 14 | RecyclerView.Adapter() { 15 | private var items = emptyList() 16 | 17 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 18 | val binding = 19 | ItemHotBinding.inflate(LayoutInflater.from(parent.context), parent, false) 20 | return ViewHolder(binding) 21 | } 22 | 23 | 24 | override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(items[position]) 25 | override fun getItemCount() = items.size 26 | 27 | inner class ViewHolder(private val binding: ItemHotBinding) : 28 | RecyclerView.ViewHolder(binding.root) { 29 | fun bind(item: ResponseTodayHotItem) { 30 | //init view 31 | binding.apply { 32 | //title 33 | txtTitle.text = item.title 34 | //image 35 | itemImg.loadImage(item.image!!) 36 | //date 37 | txtDate.text = item.date 38 | } 39 | } 40 | } 41 | 42 | fun setData(item: List) { 43 | val adapterDiffUtils = BaseDiffUtils(items, item) 44 | val diffUtils = DiffUtil.calculateDiff(adapterDiffUtils) 45 | items = item 46 | diffUtils.dispatchUpdatesTo(this) 47 | } 48 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/viewmodel/MobileViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.viewmodel 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | import androidx.lifecycle.asLiveData 7 | import androidx.lifecycle.viewModelScope 8 | import com.mehdisekoba.digiato.data.database.entity.MobileNewsEntity 9 | import com.mehdisekoba.digiato.data.model.video.ResponseMobileVideo 10 | import com.mehdisekoba.digiato.data.repository.MobileRepository 11 | import com.mehdisekoba.digiato.utils.network.NetworkRequest 12 | import com.mehdisekoba.digiato.utils.network.NetworkResponse 13 | import dagger.hilt.android.lifecycle.HiltViewModel 14 | import kotlinx.coroutines.Dispatchers.IO 15 | import kotlinx.coroutines.launch 16 | import javax.inject.Inject 17 | 18 | @HiltViewModel 19 | class MobileViewModel @Inject constructor(private val repository: MobileRepository) : 20 | ViewModel() { 21 | //category 22 | //remote 23 | private val _mobileData = MutableLiveData>() 24 | val mobileData: LiveData> = _mobileData 25 | fun callMobileApi() = viewModelScope.launch { 26 | _mobileData.value = NetworkRequest.Loading() 27 | val response = repository.remote.getMobileList() 28 | _mobileData.value = NetworkResponse(response).generateResponse() 29 | //cache 30 | val cache = _mobileData.value?.data 31 | if (cache != null) 32 | offlineCacheMobile(cache) 33 | 34 | } 35 | 36 | //local 37 | private fun saveMobile(entity: MobileNewsEntity) = viewModelScope.launch(IO) { 38 | repository.local.insertMobileNews(entity) 39 | } 40 | val readMobileDb = repository.local.loadMobileNews().asLiveData() 41 | private fun offlineCacheMobile(data: ResponseMobileVideo) { 42 | val entity = MobileNewsEntity(3, result = data) 43 | saveMobile(entity) 44 | } 45 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/ui/profile/ProfileFragment.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.ui.profile 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.widget.Toast 7 | import androidx.appcompat.app.AppCompatDelegate 8 | import androidx.lifecycle.lifecycleScope 9 | import com.mehdisekoba.digiato.R 10 | import com.mehdisekoba.digiato.data.stored.ThemeManager 11 | import com.mehdisekoba.digiato.databinding.FragmentProfileBinding 12 | import com.mehdisekoba.digiato.utils.base.BaseFragment 13 | import dagger.hilt.android.AndroidEntryPoint 14 | import kotlinx.coroutines.launch 15 | import javax.inject.Inject 16 | 17 | @AndroidEntryPoint 18 | class ProfileFragment : BaseFragment() { 19 | override val bindingInflater: (LayoutInflater) -> FragmentProfileBinding = FragmentProfileBinding::inflate 20 | 21 | @Inject 22 | lateinit var themeManager: ThemeManager 23 | 24 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 25 | super.onViewCreated(view, savedInstanceState) 26 | 27 | // Observe theme preference 28 | viewLifecycleOwner.lifecycleScope.launch { 29 | themeManager.isDarkTheme.collect { isDarkTheme -> 30 | if (binding.orderLay.switchTheme.isChecked != isDarkTheme) { 31 | binding.orderLay.switchTheme.isChecked = isDarkTheme 32 | } 33 | if (isDarkTheme){ 34 | AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) 35 | }else{ 36 | AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) 37 | } 38 | } 39 | } 40 | 41 | binding.orderLay.switchTheme.setOnCheckedChangeListener { _, isChecked -> 42 | viewLifecycleOwner.lifecycleScope.launch { 43 | themeManager.saveTheme(isChecked) 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/res/xml/fragment_splash_scene.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 17 | 22 | 27 | 32 | 33 | 34 | 35 | 36 | 37 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/ui/categories/AdapterCategories.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.ui.categories 2 | 3 | import android.content.Context 4 | import android.view.LayoutInflater 5 | import android.view.ViewGroup 6 | import androidx.recyclerview.widget.DiffUtil 7 | import androidx.recyclerview.widget.RecyclerView 8 | import com.mehdisekoba.digiato.data.model.category.ResponseCategory.ResponseCategoryItem 9 | import com.mehdisekoba.digiato.databinding.ItemCategoriesBinding 10 | import com.mehdisekoba.digiato.utils.base.BaseDiffUtils 11 | import com.mehdisekoba.digiato.utils.extensions.loadImage 12 | import dagger.hilt.android.qualifiers.ApplicationContext 13 | import javax.inject.Inject 14 | 15 | class AdapterCategories @Inject constructor(@ApplicationContext val context: Context) : 16 | RecyclerView.Adapter() { 17 | private var items = emptyList() 18 | 19 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 20 | val binding = 21 | ItemCategoriesBinding.inflate(LayoutInflater.from(parent.context), parent, false) 22 | return ViewHolder(binding) 23 | } 24 | 25 | 26 | override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(items[position]) 27 | override fun getItemCount() = items.size 28 | 29 | inner class ViewHolder(private val binding: ItemCategoriesBinding) : 30 | RecyclerView.ViewHolder(binding.root) { 31 | fun bind(item: ResponseCategoryItem) { 32 | //init view 33 | binding.apply { 34 | //image 35 | itemImage.loadImage(item.image!!) 36 | //title 37 | txtTitle.text = item.title 38 | } 39 | } 40 | } 41 | 42 | fun setData(item: List) { 43 | val adapterDiffUtils = BaseDiffUtils(items, item) 44 | val diffUtils = DiffUtil.calculateDiff(adapterDiffUtils) 45 | items = item 46 | diffUtils.dispatchUpdatesTo(this) 47 | } 48 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/ui/splash/SplashFragment.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.ui.splash 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import androidx.constraintlayout.motion.widget.MotionLayout 7 | import androidx.navigation.fragment.findNavController 8 | import com.mehdisekoba.digiato.R 9 | import com.mehdisekoba.digiato.databinding.FragmentSplashBinding 10 | import com.mehdisekoba.digiato.utils.base.BaseFragment 11 | import dagger.hilt.android.AndroidEntryPoint 12 | 13 | @AndroidEntryPoint 14 | class SplashFragment : BaseFragment() { 15 | override val bindingInflater: (inflater: LayoutInflater) -> FragmentSplashBinding 16 | get() = FragmentSplashBinding::inflate 17 | 18 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 19 | super.onViewCreated(view, savedInstanceState) 20 | //initViews 21 | binding.apply { 22 | lightProgress.on() 23 | motionLay.addTransitionListener(object : MotionLayout.TransitionListener { 24 | override fun onTransitionStarted( 25 | motionLayout: MotionLayout?, 26 | startId: Int, 27 | endId: Int 28 | ) { 29 | } 30 | 31 | override fun onTransitionChange( 32 | motionLayout: MotionLayout?, 33 | startId: Int, 34 | endId: Int, 35 | progress: Float 36 | ) { 37 | } 38 | 39 | override fun onTransitionCompleted(motionLayout: MotionLayout?, currentId: Int) { 40 | findNavController().popBackStack(R.id.splashFragment,true) 41 | findNavController().navigate(R.id.action_to_home) 42 | } 43 | 44 | override fun onTransitionTrigger( 45 | motionLayout: MotionLayout?, 46 | triggerId: Int, 47 | positive: Boolean, 48 | progress: Float 49 | ) { 50 | } 51 | 52 | }) 53 | } 54 | } 55 | 56 | 57 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/item_hot.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 20 | 21 | 34 | 47 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_help_center.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/utils/other/CustomDividerItemDecoration.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.utils.other 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.graphics.drawable.Drawable 6 | import androidx.core.content.ContextCompat 7 | import androidx.recyclerview.widget.RecyclerView 8 | import com.mehdisekoba.digiato.R 9 | 10 | class CustomDividerItemDecoration(context: Context) : RecyclerView.ItemDecoration() { 11 | private val divider: Drawable? = ContextCompat.getDrawable(context, R.drawable.divider) 12 | 13 | override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) { 14 | if (parent.layoutManager == null || divider == null) { 15 | return 16 | } 17 | 18 | val orientation = 19 | (parent.layoutManager as? RecyclerView.LayoutManager)?.canScrollHorizontally() 20 | 21 | if (orientation == true) { 22 | drawHorizontalDividers(c, parent) 23 | } else { 24 | drawVerticalDividers(c, parent) 25 | } 26 | } 27 | 28 | private fun drawVerticalDividers(canvas: Canvas, parent: RecyclerView) { 29 | val left = parent.paddingLeft 30 | val right = parent.width - parent.paddingRight 31 | 32 | for (i in 0 until parent.childCount - 1) { 33 | val child = parent.getChildAt(i) 34 | val params = child.layoutParams as RecyclerView.LayoutParams 35 | val top = child.bottom + params.bottomMargin 36 | val bottom = top + divider!!.intrinsicHeight 37 | 38 | divider.setBounds(left, top, right, bottom) 39 | divider.draw(canvas) 40 | } 41 | } 42 | 43 | private fun drawHorizontalDividers(canvas: Canvas, parent: RecyclerView) { 44 | val left = parent.paddingLeft 45 | val right = parent.width - parent.paddingRight 46 | val childCount = parent.childCount 47 | for (i in 0 until childCount - 1) { 48 | val child = parent.getChildAt(i) 49 | val params = child.layoutParams as RecyclerView.LayoutParams 50 | val top = child.bottom + params.bottomMargin 51 | val bottom = top + divider!!.intrinsicHeight 52 | 53 | divider.setBounds(left, top, right, bottom) 54 | divider.draw(canvas) 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/ui/home/adapters/AdapterSuggestions.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.ui.home.adapters 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.view.LayoutInflater 6 | import android.view.ViewGroup 7 | import androidx.recyclerview.widget.DiffUtil 8 | import androidx.recyclerview.widget.RecyclerView 9 | import com.mehdisekoba.digiato.R 10 | import com.mehdisekoba.digiato.data.model.home.ResponseSuggestions.ResponseSuggestionsItem 11 | import com.mehdisekoba.digiato.databinding.ItemSuggestionsBinding 12 | import com.mehdisekoba.digiato.utils.base.BaseDiffUtils 13 | import com.mehdisekoba.digiato.utils.extensions.loadImage 14 | import dagger.hilt.android.qualifiers.ApplicationContext 15 | import javax.inject.Inject 16 | 17 | class AdapterSuggestions @Inject constructor(@ApplicationContext val context: Context) : 18 | RecyclerView.Adapter() { 19 | private var items = emptyList() 20 | 21 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 22 | val binding = 23 | ItemSuggestionsBinding.inflate(LayoutInflater.from(parent.context), parent, false) 24 | return ViewHolder(binding) 25 | } 26 | 27 | 28 | override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(items[position]) 29 | override fun getItemCount() = items.size 30 | 31 | inner class ViewHolder(private val binding: ItemSuggestionsBinding) : 32 | RecyclerView.ViewHolder(binding.root) { 33 | @SuppressLint("SetTextI18n") 34 | fun bind(item: ResponseSuggestionsItem) { 35 | //init view 36 | binding.apply { 37 | //image 38 | itemImg.loadImage(item.image!!) 39 | //category 40 | txtCategory.text = item.category 41 | //title 42 | txtTitle.text = item.title 43 | //author 44 | txtAuthor.text = 45 | "${context.getText(R.string.Written_by)} ${item.author} | ${item.date}" 46 | } 47 | } 48 | } 49 | 50 | fun setData(item: List) { 51 | val adapterDiffUtils = BaseDiffUtils(items, item) 52 | val diffUtils = DiffUtil.calculateDiff(adapterDiffUtils) 53 | items = item 54 | diffUtils.dispatchUpdatesTo(this) 55 | } 56 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/ui/home/adapters/AdapterToday.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.ui.home.adapters 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.view.LayoutInflater 6 | import android.view.ViewGroup 7 | import androidx.recyclerview.widget.DiffUtil 8 | import androidx.recyclerview.widget.RecyclerView 9 | import com.mehdisekoba.digiato.R 10 | import com.mehdisekoba.digiato.data.model.home.ResponseToday.ResponseTodayItem 11 | import com.mehdisekoba.digiato.databinding.ItemTodayBinding 12 | import com.mehdisekoba.digiato.utils.base.BaseDiffUtils 13 | import com.mehdisekoba.digiato.utils.extensions.loadImage 14 | import dagger.hilt.android.qualifiers.ApplicationContext 15 | import javax.inject.Inject 16 | 17 | class AdapterToday @Inject constructor(@ApplicationContext val context: Context) : 18 | RecyclerView.Adapter() { 19 | private var items = emptyList() 20 | 21 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 22 | val binding = 23 | ItemTodayBinding.inflate(LayoutInflater.from(parent.context), parent, false) 24 | return ViewHolder(binding) 25 | } 26 | 27 | 28 | override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(items[position]) 29 | override fun getItemCount() = items.size 30 | 31 | inner class ViewHolder(private val binding: ItemTodayBinding) : 32 | RecyclerView.ViewHolder(binding.root) { 33 | @SuppressLint("SetTextI18n") 34 | fun bind(item: ResponseTodayItem) { 35 | //init view 36 | binding.apply { 37 | //category 38 | txtCategory.text = item.category 39 | //title 40 | txtTitle.text = item.title 41 | //image 42 | itemImage.loadImage(item.image!!) 43 | //description 44 | txtDescription.text = item.description 45 | //avatar 46 | avatarAuthor.loadImage(item.authorAvatar!!) 47 | //author 48 | txtAuthor.text = "${context.getText(R.string.Written_by)} ${item.author} | ${item.date}" 49 | } 50 | } 51 | } 52 | 53 | fun setData(item: List) { 54 | val adapterDiffUtils = BaseDiffUtils(items, item) 55 | val diffUtils = DiffUtil.calculateDiff(adapterDiffUtils) 56 | items = item 57 | diffUtils.dispatchUpdatesTo(this) 58 | } 59 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/utils/di/NetworkModule.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.utils.di 2 | 3 | import android.content.Context 4 | import com.google.gson.Gson 5 | import com.google.gson.GsonBuilder 6 | import com.mehdisekoba.digiato.BuildConfig 7 | import com.mehdisekoba.digiato.data.network.ApiServices 8 | import com.mehdisekoba.digiato.utils.BASE_URL 9 | import com.mehdisekoba.digiato.utils.CONNECTION_TIME 10 | import com.mehdisekoba.digiato.utils.NAMED_PING 11 | import com.mehdisekoba.digiato.utils.PING_INTERVAL 12 | import dagger.Module 13 | import dagger.Provides 14 | import dagger.hilt.InstallIn 15 | import dagger.hilt.android.qualifiers.ApplicationContext 16 | import dagger.hilt.components.SingletonComponent 17 | import kotlinx.coroutines.flow.first 18 | import kotlinx.coroutines.runBlocking 19 | import okhttp3.OkHttpClient 20 | import okhttp3.logging.HttpLoggingInterceptor 21 | import retrofit2.Retrofit 22 | import retrofit2.converter.gson.GsonConverterFactory 23 | import java.util.concurrent.TimeUnit 24 | import javax.inject.Named 25 | import javax.inject.Singleton 26 | 27 | @Module 28 | @InstallIn(SingletonComponent::class) 29 | class NetworkModule { 30 | @Provides 31 | @Singleton 32 | fun provideBaseUrl() = BASE_URL 33 | 34 | @Provides 35 | @Singleton 36 | fun provideGson(): Gson = GsonBuilder().setLenient().create() 37 | 38 | @Provides 39 | @Singleton 40 | fun provideClient( 41 | timeOut: Long, 42 | @Named(NAMED_PING) ping: Long, 43 | interceptor: HttpLoggingInterceptor, 44 | ) = OkHttpClient.Builder() 45 | .addInterceptor(interceptor) 46 | .writeTimeout(timeOut, TimeUnit.SECONDS) 47 | .readTimeout(timeOut, TimeUnit.SECONDS) 48 | .connectTimeout(timeOut, TimeUnit.SECONDS) 49 | .pingInterval(ping, TimeUnit.SECONDS) 50 | .build() 51 | 52 | 53 | @Provides 54 | @Singleton 55 | fun provideInterceptor() = HttpLoggingInterceptor().apply { 56 | level = 57 | if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY else HttpLoggingInterceptor.Level.NONE 58 | } 59 | 60 | @Provides 61 | @Singleton 62 | fun provideTimeout() = CONNECTION_TIME 63 | 64 | @Provides 65 | @Singleton 66 | @Named(NAMED_PING) 67 | fun providePingInterval() = PING_INTERVAL 68 | 69 | @Provides 70 | @Singleton 71 | fun provideRetrofit(baseUrl: String, gson: Gson, client: OkHttpClient): ApiServices = 72 | Retrofit.Builder() 73 | .baseUrl(baseUrl) 74 | .client(client) 75 | .addConverterFactory(GsonConverterFactory.create(gson)) 76 | .build() 77 | .create(ApiServices::class.java) 78 | } 79 | 80 | 81 | -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/ui/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.ui 2 | 3 | import android.os.Bundle 4 | import androidx.activity.enableEdgeToEdge 5 | import androidx.appcompat.app.AppCompatDelegate 6 | import androidx.core.view.isVisible 7 | import androidx.lifecycle.lifecycleScope 8 | import androidx.navigation.fragment.NavHostFragment 9 | import androidx.navigation.ui.setupWithNavController 10 | import com.mehdisekoba.digiato.R 11 | import com.mehdisekoba.digiato.data.stored.ThemeManager 12 | import com.mehdisekoba.digiato.databinding.ActivityMainBinding 13 | import com.mehdisekoba.digiato.utils.base.BaseActivity 14 | import dagger.hilt.android.AndroidEntryPoint 15 | import kotlinx.coroutines.delay 16 | import kotlinx.coroutines.launch 17 | import javax.inject.Inject 18 | 19 | @AndroidEntryPoint 20 | @Suppress("DEPRECATION") 21 | class MainActivity : BaseActivity() { 22 | //binding 23 | private var _binding: ActivityMainBinding? = null 24 | private val binding get() = _binding!! 25 | 26 | @Inject 27 | lateinit var themeManager: ThemeManager 28 | 29 | //other 30 | private lateinit var navHost: NavHostFragment 31 | override fun onCreate(savedInstanceState: Bundle?) { 32 | super.onCreate(savedInstanceState) 33 | enableEdgeToEdge() 34 | _binding = ActivityMainBinding.inflate(layoutInflater) 35 | setContentView(binding.root) 36 | //init 37 | navHost = supportFragmentManager.findFragmentById(R.id.navHost) as NavHostFragment 38 | // Bottom nav menu 39 | binding.bottomNav.apply { 40 | setupWithNavController(navHost.navController) 41 | // Disable double click on items 42 | setOnNavigationItemReselectedListener {} 43 | } 44 | // Gone bottom menu 45 | navHost.navController.addOnDestinationChangedListener { _, destination, _ -> 46 | binding.apply { 47 | when (destination.id) { 48 | R.id.splashFragment -> bottomNav.isVisible = false 49 | else -> bottomNav.isVisible = true 50 | } 51 | } 52 | } 53 | lifecycleScope.launch { 54 | themeManager.isDarkTheme.collect { isDarkTheme -> 55 | if (isDarkTheme){ 56 | AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) 57 | }else{ 58 | AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) 59 | } 60 | } 61 | } 62 | 63 | } 64 | 65 | 66 | override fun onNavigateUp(): Boolean = 67 | navHost.navController.navigateUp() || super.onNavigateUp() 68 | 69 | override fun onDestroy() { 70 | super.onDestroy() 71 | _binding = null 72 | } 73 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/ui/video/AdapterMobile.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.ui.video 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.view.LayoutInflater 6 | import android.view.ViewGroup 7 | import androidx.recyclerview.widget.DiffUtil 8 | import androidx.recyclerview.widget.RecyclerView 9 | import com.mehdisekoba.digiato.R 10 | import com.mehdisekoba.digiato.data.model.video.ResponseMobileVideo 11 | import com.mehdisekoba.digiato.data.model.video.ResponseMobileVideo.ResponseMobileVideoItem 12 | import com.mehdisekoba.digiato.databinding.ItemMobileBinding 13 | import com.mehdisekoba.digiato.utils.base.BaseDiffUtils 14 | import com.mehdisekoba.digiato.utils.extensions.loadImage 15 | import dagger.hilt.android.qualifiers.ApplicationContext 16 | import javax.inject.Inject 17 | import kotlin.random.Random 18 | 19 | class AdapterMobile @Inject constructor(@ApplicationContext val context: Context) : 20 | RecyclerView.Adapter() { 21 | private var items = emptyList() 22 | 23 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 24 | val binding = 25 | ItemMobileBinding.inflate(LayoutInflater.from(parent.context), parent, false) 26 | return ViewHolder(binding) 27 | } 28 | 29 | 30 | override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(items[position]) 31 | override fun getItemCount() = items.size 32 | 33 | inner class ViewHolder(private val binding: ItemMobileBinding) : 34 | RecyclerView.ViewHolder(binding.root) { 35 | @SuppressLint("SetTextI18n") 36 | fun bind(item: ResponseMobileVideoItem) { 37 | //init view 38 | binding.apply { 39 | //image 40 | itemImage.loadImage(item.image!!) 41 | //author 42 | imgAuthor.loadImage(item.authorAvatar!!) 43 | txtAuthorName.text = "${context.getString(R.string.Written_by)} ${item.author} | ${item.date}" 44 | //title 45 | txtTitle.text = item.title 46 | //description 47 | txtDescription.text = item.description 48 | //count 49 | txtFavCount.text = generateRandomValue().toString() 50 | txtCommentCount.text = generateRandomValue().toString() 51 | txtShareCount.text = generateRandomValue().toString() 52 | } 53 | } 54 | private fun generateRandomValue(): Int { 55 | return Random.nextInt(10, 141) 56 | } 57 | 58 | } 59 | 60 | fun setData(item: List) { 61 | val adapterDiffUtils = BaseDiffUtils(items, item) 62 | val diffUtils = DiffUtil.calculateDiff(adapterDiffUtils) 63 | items = item 64 | diffUtils.dispatchUpdatesTo(this) 65 | } 66 | } -------------------------------------------------------------------------------- /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 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 24 | 25 | 37 | 38 | 49 | 50 | 51 | 63 | -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/ui/categories/CategoriesFragment.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.ui.categories 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import androidx.fragment.app.Fragment 7 | import androidx.fragment.app.viewModels 8 | import androidx.recyclerview.widget.GridLayoutManager 9 | import androidx.recyclerview.widget.LinearLayoutManager 10 | import com.mehdisekoba.digiato.R 11 | import com.mehdisekoba.digiato.data.model.category.ResponseCategory 12 | import com.mehdisekoba.digiato.databinding.FragmentCategoriesBinding 13 | import com.mehdisekoba.digiato.utils.base.BaseFragment 14 | import com.mehdisekoba.digiato.utils.extensions.showSnackBar 15 | import com.mehdisekoba.digiato.utils.network.NetworkRequest 16 | import com.mehdisekoba.digiato.viewmodel.CategoryViewModel 17 | import dagger.hilt.android.AndroidEntryPoint 18 | import javax.inject.Inject 19 | 20 | 21 | @AndroidEntryPoint 22 | class CategoriesFragment : BaseFragment() { 23 | override val bindingInflater: (inflater: LayoutInflater) -> FragmentCategoriesBinding 24 | get() = FragmentCategoriesBinding::inflate 25 | 26 | @Inject 27 | lateinit var adapter: AdapterCategories 28 | 29 | //other 30 | private val viewModel by viewModels() 31 | 32 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 33 | super.onViewCreated(view, savedInstanceState) 34 | //init view 35 | binding.apply { 36 | if (isNetworkAvailable) { 37 | loadCategoriesData() 38 | } else { 39 | //show error 40 | internetLay.visibility = View.VISIBLE 41 | } 42 | } 43 | } 44 | 45 | private fun loadCategoriesData() { 46 | binding.apply { 47 | viewModel.categoryData.observe(viewLifecycleOwner) { response -> 48 | when (response) { 49 | is NetworkRequest.Loading -> { 50 | veilLayoutToolbar.veil() 51 | listCategories.veil() 52 | } 53 | 54 | is NetworkRequest.Success -> { 55 | veilLayoutToolbar.unVeil() 56 | listCategories.unVeil() 57 | response.data?.let { data -> 58 | initRecyclerView(data) 59 | } 60 | } 61 | 62 | is NetworkRequest.Error -> { 63 | veilLayoutToolbar.unVeil() 64 | listCategories.unVeil() 65 | } 66 | } 67 | } 68 | } 69 | } 70 | 71 | private fun initRecyclerView(data: ResponseCategory) { 72 | adapter.setData(data) 73 | binding.listCategories.apply { 74 | setAdapter(adapter) 75 | setVeilLayout(R.layout.item_categories, false) 76 | setLayoutManager(GridLayoutManager(requireContext(), 2)) 77 | addVeiledItems(10) 78 | } 79 | } 80 | 81 | } -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.application) 3 | alias(libs.plugins.jetbrains.kotlin.android) 4 | alias(libs.plugins.ksp.plugin) 5 | alias(libs.plugins.hilt.plugin) 6 | alias(libs.plugins.safeargs.plugin) 7 | 8 | } 9 | 10 | android { 11 | namespace = "com.mehdisekoba.digiato" 12 | compileSdk = 34 13 | 14 | defaultConfig { 15 | applicationId = "com.mehdisekoba.digiato" 16 | minSdk = 24 17 | targetSdk = 34 18 | versionCode = 1 19 | versionName = "1.0" 20 | 21 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 22 | } 23 | 24 | buildTypes { 25 | release { 26 | isMinifyEnabled = false 27 | proguardFiles( 28 | getDefaultProguardFile("proguard-android-optimize.txt"), 29 | "proguard-rules.pro" 30 | ) 31 | } 32 | } 33 | compileOptions { 34 | sourceCompatibility = JavaVersion.VERSION_17 35 | targetCompatibility = JavaVersion.VERSION_17 36 | } 37 | kotlinOptions { 38 | jvmTarget = "17" 39 | } 40 | buildFeatures { 41 | viewBinding = true 42 | buildConfig = true 43 | } 44 | } 45 | 46 | dependencies { 47 | //android 48 | implementation(libs.androidx.core.ktx) 49 | implementation(libs.androidx.appcompat) 50 | implementation(libs.material) 51 | implementation(libs.androidx.activity) 52 | implementation(libs.androidx.constraintlayout) 53 | testImplementation(libs.junit) 54 | androidTestImplementation(libs.androidx.junit) 55 | androidTestImplementation(libs.androidx.espresso.core) 56 | implementation(libs.androidx.swiperefreshlayout) 57 | 58 | //room 59 | implementation(libs.androidx.room.runtime) 60 | ksp(libs.androidx.room.compiler) 61 | implementation(libs.androidx.room.ktx) 62 | // Dagger - Hilt 63 | implementation(libs.hilt) 64 | ksp(libs.hiltcompiler) 65 | // Navigation 66 | implementation(libs.navigation.ui) 67 | implementation(libs.navigation.fragment) 68 | // Retrofit 69 | implementation(libs.retrofit) 70 | implementation(libs.converter.gson) 71 | // OkHTTP client 72 | implementation(libs.okhttp) 73 | implementation(libs.interceptor) 74 | // Coroutines 75 | implementation(libs.coroutines.core) 76 | implementation(libs.coroutines.android) 77 | // Lifecycle 78 | implementation(libs.lifecycle.runtime) 79 | implementation(libs.lifecycle.livedata) 80 | implementation(libs.lifecycle.viewmodel) 81 | // Image Loading 82 | implementation(libs.coil) 83 | // Gson 84 | implementation(libs.google.gson) 85 | // Calligraphy 86 | implementation(libs.calligraphy) 87 | implementation(libs.viewpump) 88 | //shimmer 89 | implementation(libs.androidveil) 90 | //dynamic size 91 | implementation(libs.dynamicsizes) 92 | //date 93 | implementation(libs.threetenabp) 94 | //datastore 95 | implementation(libs.androidx.datastore.preferences) 96 | //divider 97 | implementation (libs.recycler.view.divider) 98 | 99 | 100 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/ui/video/VideoFragment.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.ui.video 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import androidx.core.view.isVisible 7 | import androidx.fragment.app.viewModels 8 | import androidx.recyclerview.widget.LinearLayoutManager 9 | import com.mehdisekoba.digiato.R 10 | import com.mehdisekoba.digiato.data.model.video.ResponseMobileVideo 11 | import com.mehdisekoba.digiato.databinding.FragmentVideoBinding 12 | import com.mehdisekoba.digiato.utils.base.BaseFragment 13 | import com.mehdisekoba.digiato.utils.extensions.onceObserve 14 | import com.mehdisekoba.digiato.utils.extensions.showSnackBar 15 | import com.mehdisekoba.digiato.utils.network.NetworkRequest 16 | import com.mehdisekoba.digiato.viewmodel.MobileViewModel 17 | import dagger.hilt.android.AndroidEntryPoint 18 | import javax.inject.Inject 19 | 20 | @AndroidEntryPoint 21 | class VideoFragment : BaseFragment() { 22 | override val bindingInflater: (inflater: LayoutInflater) -> FragmentVideoBinding 23 | get() = FragmentVideoBinding::inflate 24 | 25 | @Inject 26 | lateinit var adapterMobile: AdapterMobile 27 | 28 | //other 29 | private val viewModel by viewModels() 30 | 31 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 32 | super.onViewCreated(view, savedInstanceState) 33 | //init 34 | callTodayHotData() 35 | } 36 | 37 | private fun callTodayHotData() { 38 | viewModel.readMobileDb.onceObserve(viewLifecycleOwner) { database -> 39 | if (!isNetworkAvailable) { 40 | if (database.isNotEmpty()) { 41 | initRecyclerView(database[0].result) 42 | } 43 | } else { 44 | viewModel.callMobileApi() 45 | loadMobileData() 46 | 47 | } 48 | 49 | } 50 | } 51 | 52 | private fun loadMobileData() { 53 | binding.apply { 54 | viewModel.mobileData.observe(viewLifecycleOwner) { response -> 55 | when (response) { 56 | is NetworkRequest.Loading -> { 57 | veilLayoutToolbar.veil() 58 | listMobile.veil() 59 | loading.isVisible = true 60 | 61 | } 62 | 63 | is NetworkRequest.Success -> { 64 | veilLayoutToolbar.unVeil() 65 | listMobile.unVeil() 66 | loading.isVisible = false 67 | 68 | 69 | response.data?.let { data -> 70 | initRecyclerView(data) 71 | } 72 | } 73 | 74 | is NetworkRequest.Error -> { 75 | veilLayoutToolbar.unVeil() 76 | listMobile.veil() 77 | loading.isVisible = false 78 | 79 | } 80 | } 81 | } 82 | } 83 | } 84 | 85 | private fun initRecyclerView(data: ResponseMobileVideo) { 86 | adapterMobile.setData(data) 87 | binding.listMobile.apply { 88 | setAdapter(adapterMobile) 89 | setVeilLayout(R.layout.item_mobile, false) 90 | setLayoutManager(LinearLayoutManager(requireContext())) 91 | addVeiledItems(10) 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 19 | 20 | 21 | 24 | 25 | 32 | 33 | 45 | 46 | 50 | 51 | 54 | 55 | 58 | 59 | 62 | 63 | 66 | 67 | 68 | 71 | 72 | 75 | 76 | 79 | 80 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_categories.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 20 | 21 | 24 | 25 | 37 | 38 | 48 | 49 | 50 | 51 | 63 | 64 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_video.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 24 | 25 | 28 | 29 | 41 | 42 | 51 | 52 | 53 | 58 | 59 | 68 | 69 | 70 | 80 | 81 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/brand.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 119 | 120 | 122 | 123 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_today.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 23 | 24 | 34 | 35 | 48 | 49 | 62 | 63 | 73 | 74 | 85 | 86 | -------------------------------------------------------------------------------- /app/src/main/java/com/mehdisekoba/digiato/viewmodel/HomeViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.mehdisekoba.digiato.viewmodel 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | import androidx.lifecycle.asLiveData 7 | import androidx.lifecycle.viewModelScope 8 | import com.mehdisekoba.digiato.data.database.entity.NewsHotEntity 9 | import com.mehdisekoba.digiato.data.database.entity.NewsSuggestionsEntity 10 | import com.mehdisekoba.digiato.data.database.entity.NewsTodayEntity 11 | import com.mehdisekoba.digiato.data.model.home.ResponseSuggestions 12 | import com.mehdisekoba.digiato.data.model.home.ResponseToday 13 | import com.mehdisekoba.digiato.data.model.home.ResponseTodayHot 14 | import com.mehdisekoba.digiato.data.repository.HomeRepository 15 | import com.mehdisekoba.digiato.utils.network.NetworkRequest 16 | import com.mehdisekoba.digiato.utils.network.NetworkResponse 17 | import dagger.hilt.android.lifecycle.HiltViewModel 18 | import kotlinx.coroutines.Dispatchers.IO 19 | import kotlinx.coroutines.delay 20 | import kotlinx.coroutines.launch 21 | import javax.inject.Inject 22 | 23 | @HiltViewModel 24 | class HomeViewModel @Inject constructor(private val repository: HomeRepository) : ViewModel() { 25 | 26 | 27 | //remote 28 | //today hot 29 | private val _todayHotData = MutableLiveData>() 30 | val todayHotData: LiveData> = _todayHotData 31 | 32 | fun callTodayHotApi() = viewModelScope.launch { 33 | _todayHotData.value = NetworkRequest.Loading() 34 | val response = repository.remote.getToadyHotList() 35 | _todayHotData.value = NetworkResponse(response).generateResponse() 36 | //cache 37 | val cache = _todayHotData.value?.data 38 | if (cache != null) 39 | offlineCacheTodayHot(cache) 40 | } 41 | 42 | private val _suggestData = MutableLiveData>() 43 | val suggestData: LiveData> = _suggestData 44 | fun callSuggestApi() = viewModelScope.launch { 45 | _suggestData.value = NetworkRequest.Loading() 46 | val response = repository.remote.getSuggestionsNewsList() 47 | _suggestData.value = NetworkResponse(response).generateResponse() 48 | //cache 49 | val cache = _suggestData.value?.data 50 | if (cache != null) 51 | offlineCacheSuggestions(cache) 52 | } 53 | 54 | //today news 55 | private val _todayNewsData = MutableLiveData>() 56 | val todayNewsData: LiveData> = _todayNewsData 57 | fun callTodayNewsApi() = viewModelScope.launch { 58 | _todayNewsData.value = NetworkRequest.Loading() 59 | val response = repository.remote.getTodayNewsList() 60 | _todayNewsData.value = NetworkResponse(response).generateResponse() 61 | //cache 62 | val cache = _todayNewsData.value?.data 63 | if (cache != null) 64 | offlineCacheToday(cache) 65 | 66 | } 67 | 68 | //local 69 | //today news 70 | private fun saveTodayHot(entity: NewsHotEntity) = viewModelScope.launch(IO) { 71 | repository.local.insertHotNews(entity) 72 | } 73 | 74 | val readTodayHotDb = repository.local.loadHotNews().asLiveData() 75 | private fun offlineCacheTodayHot(data: ResponseTodayHot) { 76 | val entity = NewsHotEntity(0, result = data) 77 | saveTodayHot(entity) 78 | } 79 | 80 | //suggestions 81 | private fun saveSuggestions(entity: NewsSuggestionsEntity) = viewModelScope.launch(IO) { 82 | repository.local.insertSuggestionsNews(entity) 83 | } 84 | 85 | val readSuggestionsDb = repository.local.loadSuggestionsNews().asLiveData() 86 | private fun offlineCacheSuggestions(data: ResponseSuggestions) { 87 | val entity = NewsSuggestionsEntity(1, result = data) 88 | saveSuggestions(entity) 89 | } 90 | 91 | //today 92 | private fun saveToday(entity: NewsTodayEntity) = viewModelScope.launch(IO) { 93 | repository.local.insertTodayNews(entity) 94 | } 95 | 96 | val readTodayDb = repository.local.loadTodayNews().asLiveData() 97 | private fun offlineCacheToday(data: ResponseToday) { 98 | val entity = NewsTodayEntity(2, result = data) 99 | saveToday(entity) 100 | } 101 | 102 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/item_suggestions.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 15 | 16 | 23 | 24 | 28 | 29 | 42 | 43 | 49 | 50 | 63 | 64 | 79 | 80 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | agp = "8.5.1" 3 | datastorePreferences = "1.1.1" 4 | kotlin = "2.0.0" 5 | coreKtx = "1.13.1" 6 | junit = "4.13.2" 7 | junitVersion = "1.2.1" 8 | espressoCore = "3.6.1" 9 | appcompat = "1.7.0" 10 | material = "1.12.0" 11 | activity = "1.9.1" 12 | constraintlayout = "2.1.4" 13 | androidveil = "1.1.4" 14 | kspversion = "2.0.0-1.0.23" 15 | hiltversion = "2.51.1" 16 | recyclerViewDivider = "3.6.0" 17 | roomRuntime = "2.6.1" 18 | safeargs = "2.7.7" 19 | retrofit = "2.11.0" 20 | okhttp = "4.12.0" 21 | lifecycle = "2.8.4" 22 | coroutines = "1.8.1" 23 | google_gson = "2.10.1" 24 | coil = "2.6.0" 25 | swiperefreshlayout = "1.1.0" 26 | calligraphy = "3.1.1" 27 | threetenabp = "1.4.7" 28 | viewpump = "2.0.3" 29 | dynamicsizes = "1.0" 30 | 31 | [libraries] 32 | androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } 33 | androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" } 34 | androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "roomRuntime" } 35 | androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "roomRuntime" } 36 | androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "roomRuntime" } 37 | junit = { group = "junit", name = "junit", version.ref = "junit" } 38 | androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } 39 | androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } 40 | androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } 41 | material = { group = "com.google.android.material", name = "material", version.ref = "material" } 42 | androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" } 43 | androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" } 44 | androidveil = { group = "com.github.skydoves",name="androidveil", version.ref = "androidveil" } 45 | hilt = { group = "com.google.dagger", name = "hilt-android", version.ref = "hiltversion" } 46 | hiltcompiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hiltversion" } 47 | navigation_fragment = { group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref = "safeargs" } 48 | navigation_ui = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "safeargs" } 49 | recycler-view-divider = { module = "com.github.fondesa:recycler-view-divider", version.ref = "recyclerViewDivider" } 50 | retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" } 51 | converter_gson = { group = "com.squareup.retrofit2", name = "converter-gson", version.ref = "retrofit" } 52 | okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" } 53 | interceptor = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" } 54 | lifecycle_viewmodel = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycle" } 55 | lifecycle_runtime = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" } 56 | lifecycle_livedata = { group = "androidx.lifecycle", name = "lifecycle-livedata-ktx", version.ref = "lifecycle" } 57 | coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "coroutines" } 58 | coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutines" } 59 | google_gson = { group = "com.google.code.gson", name = "gson", version.ref = "google_gson" } 60 | coil = { group = "io.coil-kt", name = "coil", version.ref = "coil" } 61 | calligraphy = { group = "io.github.inflationx", name = "calligraphy3", version.ref = "calligraphy" } 62 | threetenabp = { module = "com.jakewharton.threetenabp:threetenabp", version.ref = "threetenabp" } 63 | viewpump = { group = "io.github.inflationx", name = "viewpump", version.ref = "viewpump" } 64 | dynamicsizes = { group = "com.github.MrNouri", name = "DynamicSizes", version.ref = "dynamicsizes" } 65 | androidx-swiperefreshlayout = { group = "androidx.swiperefreshlayout", name = "swiperefreshlayout", version.ref = "swiperefreshlayout" } 66 | 67 | [plugins] 68 | android-application = { id = "com.android.application", version.ref = "agp" } 69 | jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } 70 | ksp_plugin = { id = "com.google.devtools.ksp", version.ref = "kspversion" } 71 | safeargs_plugin = { id = "androidx.navigation.safeargs", version.ref = "safeargs" } 72 | hilt_plugin = { id = "com.google.dagger.hilt.android", version.ref = "hiltversion" } 73 | room_plugin={id="androidx.room",version.ref="roomRuntime"} 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Advanced Android Development Techniques 2 | 3 | This is a news application project developed using the MVVM (Model-View-ViewModel) architecture. The app fetches news data from the Digiato website, with a custom backend developed by me. The backend is hosted on my server, and the news updates in real-time whenever there is an update on the Digiato site. 4 | 5 | 6 | ## ScreenShots 7 | 8 | splash Screen light | home Screen light | 9 | :-------------------------:| :-------------------------:| 10 | | | 11 | 12 | category Screen light | video Screen light 13 | :-------------------------:|:-------------------------:| 14 | | | 15 | 16 | Profile Screen light 17 | :-------------------------:| 18 | | 19 | 20 | 21 | splash Screen dark | home Screen dark | 22 | :-------------------------:|:-------------------------:| 23 | | | 24 | 25 | category Screen dark | video Screen dark 26 | :-------------------------:|:-------------------------:| 27 | | | 28 | 29 | Profile Screen dark 30 | :-------------------------:| 31 | | 32 | 33 | 34 | ## The rest of the features are at the bottom of the video viewing page 35 | ## Features 36 | 37 | **Splash Screen** Implemented using 'MotionLayout' with custom loading animations. 38 | 39 | 40 | **Shimmer Effect** 41 | Shimmer Effect: Used for loading indicators to enhance user experience. 42 | 43 | **Dependency Injection** 44 | Dependency Injection: Implemented using Hilt for efficient dependency management. 45 | 46 | **Caching By Room** 47 | Caching: News data is cached to ensure availability when the user is offline. 48 | 49 | **Custom Divider** 50 | Custom Divider: Implemented for RecyclerView items to enhance UI. 51 | 52 | **Navigation** 53 | Navigation: Managed using the Navigation Component. 54 | 55 | **DataStore Save Theme** 56 | Data Storage: Implemented using DataStore for persistent storage. 57 | 58 | **Theme Support** 59 | Theme Support: Includes both dark and light modes, ensuring icons and text adapt to the selected theme. 60 | 61 | **Database** 62 | Database: Implemented using Room with TypeConverter and Database for data management. 63 | 64 | **Coroutines** 65 | Coroutines: Used for asynchronous operations to ensure smooth performance. 66 | 67 | **LiveData** 68 | LiveData and Flow: Used for observing data changes and managing data streams. 69 | 70 | **Entity and DAO** 71 | Entity and DAO: Implemented in Room for efficient data caching. 72 | 73 | **Custom Fonts** 74 | Custom Fonts: Added throughout the project using the Calligraphy library. 75 | 76 | **Network Dependency** 77 | Network Dependency Injection: Custom implementation for managing network dependencies. 78 | 79 | **Material Design 3** 80 | Material Design 3: The project adheres to Material Design 3 guidelines. 81 | 82 | **Persian Calendar** 83 | Persian Calendar: Implemented for users needing a localized calendar. 84 | 85 | **Custom Views** 86 | Custom Views: Developed specific custom views to enhance the UI/UX. 87 | 88 | **Purpose:** 89 | The primary goal of this project is to demonstrate the implementation of data caching and other modern Android development practices. 90 | 91 | 92 | ## Architecture 93 | 94 | - **MVVM Architecture** 95 | - This project follows the MVVM (Model-View-ViewModel) architecture pattern for a clear separation of concerns and better testability. 96 | 97 | Video 98 | 99 | [![IMAGE ALT TEXT HERE](https://img.youtube.com/vi/EgRy7tHZTKs/0.jpg)](https://www.youtube.com/embed/EgRy7tHZTKs?si=2iIJEz82Z9eK9pAj) 100 | 101 | 102 | ## Support 103 | 104 | This project includes many more capabilities. Please support the project by giving it a star and forking it. 105 | 106 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_profile.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 20 | 21 | 24 | 25 | 37 | 38 | 48 | 49 | 50 | 57 | 60 | 61 | 73 | 74 | 85 | 86 | 94 | 95 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /app/src/main/res/layout/profile_user_order.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 21 | 22 | 36 | 37 | 47 | 48 | 58 | 59 | 72 | 73 | 83 | 84 | 98 | 99 | 109 | 110 | 124 | 125 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 27 | 28 | 34 | 35 | 40 | 41 | 46 | 47 | 52 | 53 | 58 | 59 | 62 | 63 | 70 | 71 | 75 | 76 | 80 | 81 | 85 | 86 | 99 | 100 | 104 | 105 | 108 | 109 | 112 | 113 | 116 | 117 | 120 | 121 | 124 | 125 | 128 | 129 | 132 | 135 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or 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 UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | --------------------------------------------------------------------------------