├── .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 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
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 |
7 |
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 |
8 |
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 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_home.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
5 |
24 |
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 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | xmlns:android
18 |
19 | ^$
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | xmlns:.*
29 |
30 | ^$
31 |
32 |
33 | BY_NAME
34 |
35 |
36 |
37 |
38 |
39 |
40 | .*:id
41 |
42 | http://schemas.android.com/apk/res/android
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | .*:name
52 |
53 | http://schemas.android.com/apk/res/android
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | name
63 |
64 | ^$
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | style
74 |
75 | ^$
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | .*
85 |
86 | ^$
87 |
88 |
89 | BY_NAME
90 |
91 |
92 |
93 |
94 |
95 |
96 | .*
97 |
98 | http://schemas.android.com/apk/res/android
99 |
100 |
101 | ANDROID_ATTRIBUTE_ORDER
102 |
103 |
104 |
105 |
106 |
107 |
108 | .*
109 |
110 | .*
111 |
112 |
113 | BY_NAME
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
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 | [](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 |
--------------------------------------------------------------------------------